From 1f6c271b3bedd6656ae7155714423b1b39a669c1 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Wed, 16 May 2018 13:43:22 -0400 Subject: MM-8708 Remove api package (#8784) * Remove api package * Remove api dependency from cmd package * Remove EnableAPIv3 setting * Update web tests * Add more websocket tests * Move some ws and oauth tests to api4 package * Move command tests into api4 package * Test fixes * Fix msg command test * Add some app file tests --- Makefile | 2 - api/admin.go | 422 ------ api/admin_test.go | 624 -------- api/api.go | 131 -- api/api_test.go | 52 - api/apitestlib.go | 508 ------- api/channel.go | 843 ----------- api/channel_test.go | 2007 ------------------------- api/command.go | 286 ---- api/command_echo_test.go | 36 - api/command_expand_collapse_test.go | 51 - api/command_groupmsg_test.go | 58 - api/command_help_test.go | 37 - api/command_invite_people_test.go | 33 - api/command_join_test.go | 61 - api/command_leave_test.go | 68 - api/command_loadtest_test.go | 97 -- api/command_logout_test.go | 15 - api/command_me_test.go | 38 - api/command_msg_test.go | 47 - api/command_open_test.go | 12 - api/command_search_test.go | 15 - api/command_settings_test.go | 15 - api/command_shortcuts_test.go | 15 - api/command_shrug_test.go | 38 - api/command_statuses_test.go | 42 - api/command_test.go | 310 ---- api/context.go | 451 ------ api/context_test.go | 29 - api/deprecated_test.go | 4 - api/emoji.go | 228 --- api/emoji_test.go | 437 ------ api/file.go | 333 ----- api/file_test.go | 950 ------------ api/general.go | 73 - api/general_test.go | 63 - api/license.go | 100 -- api/license_test.go | 51 - api/oauth.go | 264 ---- api/oauth_test.go | 895 ------------ api/post.go | 560 ------- api/post_test.go | 1510 ------------------- api/preference.go | 85 -- api/preference_test.go | 220 --- api/reaction.go | 149 -- api/reaction_test.go | 317 ---- api/status.go | 37 - api/status_test.go | 234 --- api/team.go | 543 ------- api/team_test.go | 1196 --------------- api/user.go | 1245 ---------------- api/user_test.go | 2737 ----------------------------------- api/webhook.go | 328 ----- api/webhook_test.go | 968 ------------- api/webrtc.go | 23 - api/websocket.go | 40 - api/websocket_test.go | 381 ----- api4/api.go | 7 +- api4/apitestlib.go | 2 +- api4/commands_test.go | 449 ++++++ api4/oauth_test.go | 413 ++++++ api4/websocket_test.go | 387 +++++ app/auto_channels.go | 16 +- app/auto_environment.go | 7 +- app/auto_posts.go | 10 +- app/auto_teams.go | 7 +- app/auto_users.go | 34 +- app/command_loadtest.go | 12 +- app/diagnostics.go | 5 - app/file.go | 5 - app/file_test.go | 61 + cmd/commands/channel_test.go | 29 +- cmd/commands/roles_test.go | 4 +- cmd/commands/sampledata_test.go | 4 +- cmd/commands/server.go | 6 +- cmd/commands/team_test.go | 18 +- cmd/commands/test.go | 7 +- cmd/commands/user_test.go | 18 +- manualtesting/manual_testing.go | 34 +- manualtesting/test_autolink.go | 4 +- model/client.go | 2379 ------------------------------ model/client4.go | 72 + model/config.go | 5 - model/websocket_client.go | 31 +- utils/config.go | 1 - web/web_test.go | 5 +- web/webhook_test.go | 85 +- 87 files changed, 1539 insertions(+), 22892 deletions(-) delete mode 100644 api/admin.go delete mode 100644 api/admin_test.go delete mode 100644 api/api.go delete mode 100644 api/api_test.go delete mode 100644 api/apitestlib.go delete mode 100644 api/channel.go delete mode 100644 api/channel_test.go delete mode 100644 api/command.go delete mode 100644 api/command_echo_test.go delete mode 100644 api/command_expand_collapse_test.go delete mode 100644 api/command_groupmsg_test.go delete mode 100644 api/command_help_test.go delete mode 100644 api/command_invite_people_test.go delete mode 100644 api/command_join_test.go delete mode 100644 api/command_leave_test.go delete mode 100644 api/command_loadtest_test.go delete mode 100644 api/command_logout_test.go delete mode 100644 api/command_me_test.go delete mode 100644 api/command_msg_test.go delete mode 100644 api/command_open_test.go delete mode 100644 api/command_search_test.go delete mode 100644 api/command_settings_test.go delete mode 100644 api/command_shortcuts_test.go delete mode 100644 api/command_shrug_test.go delete mode 100644 api/command_statuses_test.go delete mode 100644 api/command_test.go delete mode 100644 api/context.go delete mode 100644 api/context_test.go delete mode 100644 api/deprecated_test.go delete mode 100644 api/emoji.go delete mode 100644 api/emoji_test.go delete mode 100644 api/file.go delete mode 100644 api/file_test.go delete mode 100644 api/general.go delete mode 100644 api/general_test.go delete mode 100644 api/license.go delete mode 100644 api/license_test.go delete mode 100644 api/oauth.go delete mode 100644 api/oauth_test.go delete mode 100644 api/post.go delete mode 100644 api/post_test.go delete mode 100644 api/preference.go delete mode 100644 api/preference_test.go delete mode 100644 api/reaction.go delete mode 100644 api/reaction_test.go delete mode 100644 api/status.go delete mode 100644 api/status_test.go delete mode 100644 api/team.go delete mode 100644 api/team_test.go delete mode 100644 api/user.go delete mode 100644 api/user_test.go delete mode 100644 api/webhook.go delete mode 100644 api/webhook_test.go delete mode 100644 api/webrtc.go delete mode 100644 api/websocket.go delete mode 100644 api/websocket_test.go create mode 100644 api4/commands_test.go delete mode 100644 model/client.go diff --git a/Makefile b/Makefile index b21f3d785..2c8ccf926 100644 --- a/Makefile +++ b/Makefile @@ -496,8 +496,6 @@ clean: stop-docker ## Clean up everything except persistant server data. rm -f mattermost.log rm -f mattermost.log.jsonl rm -f npm-debug.log - rm -f api/mattermost.log - rm -f api/mattermost.log.jsonl rm -f .prepare-go rm -f enterprise rm -f cover.out diff --git a/api/admin.go b/api/admin.go deleted file mode 100644 index 6016e48f3..000000000 --- a/api/admin.go +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - "strconv" - - "github.com/avct/uasurfer" - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitAdmin() { - api.BaseRoutes.Admin.Handle("/logs", api.ApiAdminSystemRequired(getLogs)).Methods("GET") - api.BaseRoutes.Admin.Handle("/audits", api.ApiAdminSystemRequired(getAllAudits)).Methods("GET") - api.BaseRoutes.Admin.Handle("/config", api.ApiAdminSystemRequired(getConfig)).Methods("GET") - api.BaseRoutes.Admin.Handle("/save_config", api.ApiAdminSystemRequired(saveConfig)).Methods("POST") - api.BaseRoutes.Admin.Handle("/reload_config", api.ApiAdminSystemRequired(reloadConfig)).Methods("GET") - api.BaseRoutes.Admin.Handle("/invalidate_all_caches", api.ApiAdminSystemRequired(invalidateAllCaches)).Methods("GET") - api.BaseRoutes.Admin.Handle("/test_email", api.ApiAdminSystemRequired(testEmail)).Methods("POST") - api.BaseRoutes.Admin.Handle("/recycle_db_conn", api.ApiAdminSystemRequired(recycleDatabaseConnection)).Methods("GET") - api.BaseRoutes.Admin.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", api.ApiAdminSystemRequired(getAnalytics)).Methods("GET") - api.BaseRoutes.Admin.Handle("/analytics/{name:[A-Za-z0-9_]+}", api.ApiAdminSystemRequired(getAnalytics)).Methods("GET") - api.BaseRoutes.Admin.Handle("/save_compliance_report", api.ApiAdminSystemRequired(saveComplianceReport)).Methods("POST") - api.BaseRoutes.Admin.Handle("/compliance_reports", api.ApiAdminSystemRequired(getComplianceReports)).Methods("GET") - api.BaseRoutes.Admin.Handle("/download_compliance_report/{id:[A-Za-z0-9]+}", api.ApiAdminSystemRequiredTrustRequester(downloadComplianceReport)).Methods("GET") - api.BaseRoutes.Admin.Handle("/upload_brand_image", api.ApiAdminSystemRequired(uploadBrandImage)).Methods("POST") - api.BaseRoutes.Admin.Handle("/get_brand_image", api.ApiAppHandlerTrustRequester(getBrandImage)).Methods("GET") - api.BaseRoutes.Admin.Handle("/reset_mfa", api.ApiAdminSystemRequired(adminResetMfa)).Methods("POST") - api.BaseRoutes.Admin.Handle("/reset_password", api.ApiAdminSystemRequired(adminResetPassword)).Methods("POST") - api.BaseRoutes.Admin.Handle("/ldap_sync_now", api.ApiAdminSystemRequired(ldapSyncNow)).Methods("POST") - api.BaseRoutes.Admin.Handle("/ldap_test", api.ApiAdminSystemRequired(ldapTest)).Methods("POST") - api.BaseRoutes.Admin.Handle("/saml_metadata", api.ApiAppHandler(samlMetadata)).Methods("GET") - api.BaseRoutes.Admin.Handle("/add_certificate", api.ApiAdminSystemRequired(addCertificate)).Methods("POST") - api.BaseRoutes.Admin.Handle("/remove_certificate", api.ApiAdminSystemRequired(removeCertificate)).Methods("POST") - api.BaseRoutes.Admin.Handle("/saml_cert_status", api.ApiAdminSystemRequired(samlCertificateStatus)).Methods("GET") - api.BaseRoutes.Admin.Handle("/cluster_status", api.ApiAdminSystemRequired(getClusterStatus)).Methods("GET") - api.BaseRoutes.Admin.Handle("/recently_active_users/{team_id:[A-Za-z0-9]+}", api.ApiUserRequired(getRecentlyActiveUsers)).Methods("GET") -} - -func getLogs(c *Context, w http.ResponseWriter, r *http.Request) { - lines, err := c.App.GetLogs(0, 10000) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.ArrayToJson(lines))) -} - -func getClusterStatus(c *Context, w http.ResponseWriter, r *http.Request) { - infos := c.App.GetClusterStatus() - - if c.App.Cluster != nil { - w.Header().Set(model.HEADER_CLUSTER_ID, c.App.Cluster.GetClusterId()) - } - - w.Write([]byte(model.ClusterInfosToJson(infos))) -} - -func getAllAudits(c *Context, w http.ResponseWriter, r *http.Request) { - if audits, err := c.App.GetAudits("", 200); err != nil { - c.Err = err - return - } else if c.HandleEtag(audits.Etag(), "Get All Audits", w, r) { - return - } else { - etag := audits.Etag() - if len(etag) > 0 { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - } - - w.Write([]byte(audits.ToJson())) - return - } -} - -func getConfig(c *Context, w http.ResponseWriter, r *http.Request) { - cfg := c.App.GetConfig() - w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Write([]byte(cfg.ToJson())) -} - -func reloadConfig(c *Context, w http.ResponseWriter, r *http.Request) { - c.App.ReloadConfig() - w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - ReturnStatusOK(w) -} - -func invalidateAllCaches(c *Context, w http.ResponseWriter, r *http.Request) { - err := c.App.InvalidateAllCaches() - if err != nil { - c.Err = err - return - } - - w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - ReturnStatusOK(w) -} - -func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) { - cfg := model.ConfigFromJson(r.Body) - if cfg == nil { - c.SetInvalidParam("saveConfig", "config") - return - } - - // Do not allow plugin uploads to be toggled through the API - cfg.PluginSettings.EnableUploads = c.App.GetConfig().PluginSettings.EnableUploads - - err := c.App.SaveConfig(cfg, true) - if err != nil { - c.Err = err - return - } - - c.LogAudit("") - ReturnStatusOK(w) -} - -func recycleDatabaseConnection(c *Context, w http.ResponseWriter, r *http.Request) { - c.App.RecycleDatabaseConnection() - w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - ReturnStatusOK(w) -} - -func testEmail(c *Context, w http.ResponseWriter, r *http.Request) { - cfg := model.ConfigFromJson(r.Body) - if cfg == nil { - c.SetInvalidParam("testEmail", "config") - return - } - - err := c.App.TestEmail(c.Session.UserId, cfg) - if err != nil { - c.Err = err - return - } - - m := make(map[string]string) - m["SUCCESS"] = "true" - w.Write([]byte(model.MapToJson(m))) -} - -func getComplianceReports(c *Context, w http.ResponseWriter, r *http.Request) { - crs, err := c.App.GetComplianceReports(0, 10000) - if err != nil { - c.Err = err - return - } - w.Write([]byte(crs.ToJson())) -} - -func saveComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { - job := model.ComplianceFromJson(r.Body) - if job == nil { - c.SetInvalidParam("saveComplianceReport", "compliance") - return - } - - job.UserId = c.Session.UserId - - rjob, err := c.App.SaveComplianceReport(job) - if err != nil { - c.Err = err - return - } - - c.LogAudit("") - w.Write([]byte(rjob.ToJson())) -} - -func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - id := params["id"] - if len(id) != 26 { - c.SetInvalidParam("downloadComplianceReport", "id") - return - } - - job, err := c.App.GetComplianceReport(id) - if err != nil { - c.Err = err - return - } - - reportBytes, err := c.App.GetComplianceFile(job) - if err != nil { - c.Err = err - return - } - - c.LogAudit("downloaded " + job.Desc) - - w.Header().Set("Cache-Control", "max-age=2592000, public") - w.Header().Set("Content-Length", strconv.Itoa(len(reportBytes))) - w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer - - // attach extra headers to trigger a download on IE, Edge, and Safari - ua := uasurfer.Parse(r.UserAgent()) - - w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"") - - if ua.Browser.Name == uasurfer.BrowserIE || ua.Browser.Name == uasurfer.BrowserSafari { - // trim off anything before the final / so we just get the file's name - w.Header().Set("Content-Type", "application/octet-stream") - } - - w.Write(reportBytes) -} - -func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - teamId := params["id"] - name := params["name"] - - rows, err := c.App.GetAnalytics(name, teamId) - if err != nil { - c.Err = err - return - } - - if rows == nil { - c.SetInvalidParam("getAnalytics", "name") - return - } - - w.Write([]byte(rows.ToJson())) -} - -func uploadBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { - if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize { - c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge) - return - } - - if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil { - c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.parse.app_error", nil, "", http.StatusBadRequest) - return - } - - m := r.MultipartForm - - imageArray, ok := m.File["image"] - if !ok { - c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.no_file.app_error", nil, "", http.StatusBadRequest) - c.Err.StatusCode = http.StatusBadRequest - return - } - - if len(imageArray) <= 0 { - c.Err = model.NewAppError("uploadBrandImage", "api.admin.upload_brand_image.array.app_error", nil, "", http.StatusBadRequest) - c.Err.StatusCode = http.StatusBadRequest - return - } - - if err := c.App.SaveBrandImage(imageArray[0]); err != nil { - c.Err = err - return - } - - c.LogAudit("") - - ReturnStatusOK(w) -} - -func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { - if img, err := c.App.GetBrandImage(); err != nil { - w.Write(nil) - } else { - w.Header().Set("Content-Type", "image/png") - w.Write(img) - } -} - -func adminResetMfa(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("adminResetMfa", "user_id") - return - } - - if err := c.App.DeactivateMfa(userId); err != nil { - c.Err = err - return - } - - c.LogAudit("") - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func adminResetPassword(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("adminResetPassword", "user_id") - return - } - - newPassword := props["new_password"] - if err := c.App.IsPasswordValid(newPassword); err != nil { - c.Err = err - return - } - - if err := c.App.UpdatePasswordByUserIdSendEmail(userId, newPassword, c.T("api.user.reset_password.method")); err != nil { - c.Err = err - return - } - - c.LogAudit("") - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) { - c.App.SyncLdap() - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func ldapTest(c *Context, w http.ResponseWriter, r *http.Request) { - if err := c.App.TestLdap(); err != nil { - c.Err = err - return - } - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func samlMetadata(c *Context, w http.ResponseWriter, r *http.Request) { - if result, err := c.App.GetSamlMetadata(); err != nil { - c.Err = model.NewAppError("loginWithSaml", "api.admin.saml.metadata.app_error", nil, "err="+err.Message, http.StatusInternalServerError) - return - } else { - w.Header().Set("Content-Type", "application/xml") - w.Header().Set("Content-Disposition", "attachment; filename=\"metadata.xml\"") - w.Write([]byte(result)) - } -} - -func addCertificate(c *Context, w http.ResponseWriter, r *http.Request) { - err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - m := r.MultipartForm - - fileArray, ok := m.File["certificate"] - if !ok { - c.Err = model.NewAppError("addCertificate", "api.admin.add_certificate.no_file.app_error", nil, "", http.StatusBadRequest) - c.Err.StatusCode = http.StatusBadRequest - return - } - - if len(fileArray) <= 0 { - c.Err = model.NewAppError("addCertificate", "api.admin.add_certificate.array.app_error", nil, "", http.StatusBadRequest) - c.Err.StatusCode = http.StatusBadRequest - return - } - - fileData := fileArray[0] - - if err := app.WriteSamlFile(fileData); err != nil { - c.Err = err - return - } - ReturnStatusOK(w) -} - -func removeCertificate(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - if err := app.RemoveSamlFile(props["filename"]); err != nil { - c.Err = err - return - } - - ReturnStatusOK(w) -} - -func samlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request) { - status := c.App.GetSamlCertificateStatus() - - statusMap := map[string]interface{}{} - statusMap["IdpCertificateFile"] = status.IdpCertificateFile - statusMap["PrivateKeyFile"] = status.PrivateKeyFile - statusMap["PublicCertificateFile"] = status.PublicCertificateFile - - w.Write([]byte(model.StringInterfaceToJson(statusMap))) -} - -func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) { - if profiles, err := c.App.GetRecentlyActiveUsersForTeam(c.TeamId); err != nil { - c.Err = err - return - } else { - for _, p := range profiles { - sanitizeProfile(c, p) - } - - w.Write([]byte(model.UserMapToJson(profiles))) - } -} diff --git a/api/admin_test.go b/api/admin_test.go deleted file mode 100644 index dfa6cd3b9..000000000 --- a/api/admin_test.go +++ /dev/null @@ -1,624 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - "strings" - "testing" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/stretchr/testify/assert" -) - -func TestGetLogs(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - if _, err := th.BasicClient.GetLogs(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if logs, err := th.SystemAdminClient.GetLogs(); err != nil { - t.Fatal(err) - } else if len(logs.Data.([]string)) <= 0 { - t.Fatal() - } -} - -func TestGetClusterInfos(t *testing.T) { - if testing.Short() { - t.SkipNow() - } - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - if _, err := th.BasicClient.GetClusterStatus(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if _, err := th.SystemAdminClient.GetClusterStatus(); err != nil { - t.Fatal(err) - } -} - -func TestGetAllAudits(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.GetAllAudits(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if audits, err := th.SystemAdminClient.GetAllAudits(); err != nil { - t.Fatal(err) - } else if len(audits.Data.(model.Audits)) <= 0 { - t.Fatal() - } -} - -func TestGetConfig(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.GetConfig(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if result, err := th.SystemAdminClient.GetConfig(); err != nil { - t.Fatal(err) - } else { - cfg := result.Data.(*model.Config) - - if len(cfg.TeamSettings.SiteName) == 0 { - t.Fatal() - } - - if *cfg.LdapSettings.BindPassword != model.FAKE_SETTING && len(*cfg.LdapSettings.BindPassword) != 0 { - t.Fatal("did not sanitize properly") - } - if *cfg.FileSettings.PublicLinkSalt != model.FAKE_SETTING { - t.Fatal("did not sanitize properly") - } - if cfg.FileSettings.AmazonS3SecretAccessKey != model.FAKE_SETTING && len(cfg.FileSettings.AmazonS3SecretAccessKey) != 0 { - t.Fatal("did not sanitize properly") - } - if cfg.EmailSettings.InviteSalt != model.FAKE_SETTING { - t.Fatal("did not sanitize properly") - } - if cfg.EmailSettings.SMTPPassword != model.FAKE_SETTING && len(cfg.EmailSettings.SMTPPassword) != 0 { - t.Fatal("did not sanitize properly") - } - if cfg.GitLabSettings.Secret != model.FAKE_SETTING && len(cfg.GitLabSettings.Secret) != 0 { - t.Fatal("did not sanitize properly") - } - if *cfg.SqlSettings.DataSource != model.FAKE_SETTING { - t.Fatal("did not sanitize properly") - } - if cfg.SqlSettings.AtRestEncryptKey != model.FAKE_SETTING { - t.Fatal("did not sanitize properly") - } - if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceReplicas, " "), model.FAKE_SETTING) && len(cfg.SqlSettings.DataSourceReplicas) != 0 { - t.Fatal("did not sanitize properly") - } - } -} - -func TestReloadConfig(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.ReloadConfig(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if _, err := th.SystemAdminClient.ReloadConfig(); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) -} - -func TestInvalidateAllCache(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.InvalidateAllCaches(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if _, err := th.SystemAdminClient.InvalidateAllCaches(); err != nil { - t.Fatal(err) - } -} - -func TestSaveConfig(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.SaveConfig(th.App.Config()); err == nil { - t.Fatal("Shouldn't have permissions") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = false }) - - if _, err := th.SystemAdminClient.SaveConfig(th.App.Config()); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - - // Should not be able to modify PluginSettings.EnableUploads - oldEnableUploads := *th.App.GetConfig().PluginSettings.EnableUploads - cfg := &model.Config{} - cfg.SetDefaults() - *cfg.PluginSettings.EnableUploads = !oldEnableUploads - - if _, err := th.SystemAdminClient.SaveConfig(cfg); err != nil { - t.Fatal(err) - } - - assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads) -} - -func TestRecycleDatabaseConnection(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.RecycleDatabaseConnection(); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if _, err := th.SystemAdminClient.RecycleDatabaseConnection(); err != nil { - t.Fatal(err) - } -} - -func TestEmailTest(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - SendEmailNotifications := th.App.Config().EmailSettings.SendEmailNotifications - SMTPServer := th.App.Config().EmailSettings.SMTPServer - SMTPPort := th.App.Config().EmailSettings.SMTPPort - FeedbackEmail := th.App.Config().EmailSettings.FeedbackEmail - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SendEmailNotifications = SendEmailNotifications }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPServer = SMTPServer }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPPort = SMTPPort }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.FeedbackEmail = FeedbackEmail }) - }() - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SendEmailNotifications = false }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPServer = "" }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPPort = "" }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.FeedbackEmail = "" }) - - if _, err := th.BasicClient.TestEmail(th.App.Config()); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if _, err := th.SystemAdminClient.TestEmail(th.App.Config()); err == nil { - t.Fatal("should have errored") - } else { - if err.Id != "api.admin.test_email.missing_server" { - t.Fatal(err) - } - } -} - -func TestLdapTest(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.TestLdap(th.App.Config()); err == nil { - t.Fatal("Shouldn't have permissions") - } - - if _, err := th.SystemAdminClient.TestLdap(th.App.Config()); err == nil { - t.Fatal("should have errored") - } -} - -func TestGetTeamAnalyticsStandard(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - th.CreatePrivateChannel(th.BasicClient, th.BasicTeam) - - if _, err := th.BasicClient.GetTeamAnalytics(th.BasicTeam.Id, "standard"); err == nil { - t.Fatal("Shouldn't have permissions") - } - - maxUsersForStats := *th.App.Config().AnalyticsSettings.MaxUsersForStatistics - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnalyticsSettings.MaxUsersForStatistics = maxUsersForStats }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnalyticsSettings.MaxUsersForStatistics = 1000000 }) - - if result, err := th.SystemAdminClient.GetTeamAnalytics(th.BasicTeam.Id, "standard"); err != nil { - t.Fatal(err) - } else { - rows := result.Data.(model.AnalyticsRows) - - if rows[0].Name != "channel_open_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[0].Value != 4 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Name != "channel_private_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Value != 1 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Name != "post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Value != 9 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Name != "unique_user_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Value != 2 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Name != "team_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Value == 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - } - - if result, err := th.SystemAdminClient.GetSystemAnalytics("standard"); err != nil { - t.Fatal(err) - } else { - rows := result.Data.(model.AnalyticsRows) - - if rows[0].Name != "channel_open_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[0].Value < 3 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Name != "channel_private_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Value == 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Name != "post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Value == 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Name != "unique_user_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Value == 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Name != "team_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Value == 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnalyticsSettings.MaxUsersForStatistics = 1 }) - - if result, err := th.SystemAdminClient.GetSystemAnalytics("standard"); err != nil { - t.Fatal(err) - } else { - rows := result.Data.(model.AnalyticsRows) - - if rows[2].Name != "post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Value != -1 { - t.Log(rows.ToJson()) - t.Fatal() - } - } -} - -func TestGetTeamAnalyticsExtra(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - th.CreatePost(th.BasicClient, th.BasicChannel) - - if _, err := th.BasicClient.GetTeamAnalytics("", "extra_counts"); err == nil { - t.Fatal("Shouldn't have permissions") - } - - maxUsersForStats := *th.App.Config().AnalyticsSettings.MaxUsersForStatistics - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnalyticsSettings.MaxUsersForStatistics = maxUsersForStats }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnalyticsSettings.MaxUsersForStatistics = 1000000 }) - - if result, err := th.SystemAdminClient.GetTeamAnalytics(th.BasicTeam.Id, "extra_counts"); err != nil { - t.Fatal(err) - } else { - rows := result.Data.(model.AnalyticsRows) - - if rows[0].Name != "file_post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[0].Value != 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Name != "hashtag_post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Value != 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Name != "incoming_webhook_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Value != 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Name != "outgoing_webhook_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Value != 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Name != "command_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Value != 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[5].Name != "session_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[5].Value == 0 { - t.Log(rows.ToJson()) - t.Fatal() - } - } - - if result, err := th.SystemAdminClient.GetSystemAnalytics("extra_counts"); err != nil { - t.Fatal(err) - } else { - rows := result.Data.(model.AnalyticsRows) - - if rows[0].Name != "file_post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Name != "hashtag_post_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[2].Name != "incoming_webhook_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[3].Name != "outgoing_webhook_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[4].Name != "command_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[5].Name != "session_count" { - t.Log(rows.ToJson()) - t.Fatal() - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnalyticsSettings.MaxUsersForStatistics = 1 }) - - if result, err := th.SystemAdminClient.GetSystemAnalytics("extra_counts"); err != nil { - t.Fatal(err) - } else { - rows := result.Data.(model.AnalyticsRows) - - if rows[0].Value != -1 { - t.Log(rows.ToJson()) - t.Fatal() - } - - if rows[1].Value != -1 { - t.Log(rows.ToJson()) - t.Fatal() - } - } -} - -func TestAdminResetMfa(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if _, err := th.BasicClient.AdminResetMfa("12345678901234567890123456"); err == nil { - t.Fatal("should have failed - not an admin") - } - - if _, err := th.SystemAdminClient.AdminResetMfa(""); err == nil { - t.Fatal("should have failed - empty user id") - } - - if _, err := th.SystemAdminClient.AdminResetMfa("12345678901234567890123456"); err == nil { - t.Fatal("should have failed - bad user id") - } - - if _, err := th.SystemAdminClient.AdminResetMfa(th.BasicUser.Id); err == nil { - t.Fatal("should have failed - not licensed or configured") - } - - // need to add more test cases when enterprise bits can be loaded into tests -} - -func TestAdminResetPassword(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - if _, err := Client.AdminResetPassword("", "newpwd1"); err == nil { - t.Fatal("Should have errored - empty user id") - } - - if _, err := Client.AdminResetPassword("123", "newpwd1"); err == nil { - t.Fatal("Should have errored - bad user id") - } - - if _, err := Client.AdminResetPassword("12345678901234567890123456", "newpwd1"); err == nil { - t.Fatal("Should have errored - bad user id") - } - - if _, err := Client.AdminResetPassword("12345678901234567890123456", "newp"); err == nil { - t.Fatal("Should have errored - password too short") - } - - authData := model.NewId() - user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: &authData, AuthService: "random"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - if _, err := Client.AdminResetPassword(user.Id, "newpwd1"); err != nil { - t.Fatal(err) - } - - Client.Logout() - Client.Must(Client.LoginById(user.Id, "newpwd1")) - Client.SetTeamId(team.Id) - - if _, err := Client.AdminResetPassword(user.Id, "newpwd1"); err == nil { - t.Fatal("Should have errored - not system admin") - } -} - -func TestAdminLdapSyncNow(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - - if _, err := Client.LdapSyncNow(); err != nil { - t.Fatal("Returned Failure") - } -} - -// Needs more work -func TestGetRecentlyActiveUsers(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if userMap, err := th.BasicClient.GetRecentlyActiveUsers(th.BasicTeam.Id); err != nil { - t.Fatal(err) - } else if len(userMap.Data.(map[string]*model.User)) >= 2 { - t.Fatal("should have been at least 2") - } -} - -func TestDisableAPIv3(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - enableAPIv3 := *th.App.Config().ServiceSettings.EnableAPIv3 - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableAPIv3 = enableAPIv3 }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableAPIv3 = false }) - - _, err := Client.GetUser(th.BasicUser.Id, "") - - if err.StatusCode != http.StatusNotImplemented { - t.Fatal("wrong error code") - } - - if err.Id != "api.context.v3_disabled.app_error" { - t.Fatal("wrong error message") - } -} diff --git a/api/api.go b/api/api.go deleted file mode 100644 index 2b226bbeb..000000000 --- a/api/api.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" - - _ "github.com/nicksnyder/go-i18n/i18n" -) - -type Routes struct { - Root *mux.Router // '' - ApiRoot *mux.Router // 'api/v3' - - Users *mux.Router // 'api/v3/users' - NeedUser *mux.Router // 'api/v3/users/{user_id:[A-Za-z0-9]+}' - - Teams *mux.Router // 'api/v3/teams' - NeedTeam *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}' - - Channels *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels' - NeedChannel *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}' - NeedChannelName *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/name/{channel_name:[A-Za-z0-9_-]+}' - - Posts *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}/posts' - NeedPost *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}' - - Commands *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/commands' - Hooks *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/hooks' - - TeamFiles *mux.Router // 'api/v3/teams/{team_id:[A-Za-z0-9]+}/files' - Files *mux.Router // 'api/v3/files' - NeedFile *mux.Router // 'api/v3/files/{file_id:[A-Za-z0-9]+}' - - OAuth *mux.Router // 'api/v3/oauth' - - Admin *mux.Router // 'api/v3/admin' - - General *mux.Router // 'api/v3/general' - - Preferences *mux.Router // 'api/v3/preferences' - - License *mux.Router // 'api/v3/license' - - Public *mux.Router // 'api/v3/public' - - Emoji *mux.Router // 'api/v3/emoji' - - Webrtc *mux.Router // 'api/v3/webrtc' -} - -type API struct { - App *app.App - BaseRoutes *Routes -} - -func Init(a *app.App, root *mux.Router) *API { - api := &API{ - App: a, - BaseRoutes: &Routes{}, - } - api.BaseRoutes.Root = root - api.BaseRoutes.ApiRoot = root.PathPrefix(model.API_URL_SUFFIX_V3).Subrouter() - api.BaseRoutes.Users = api.BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter() - api.BaseRoutes.NeedUser = api.BaseRoutes.Users.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter() - api.BaseRoutes.Teams = api.BaseRoutes.ApiRoot.PathPrefix("/teams").Subrouter() - api.BaseRoutes.NeedTeam = api.BaseRoutes.Teams.PathPrefix("/{team_id:[A-Za-z0-9]+}").Subrouter() - api.BaseRoutes.Channels = api.BaseRoutes.NeedTeam.PathPrefix("/channels").Subrouter() - api.BaseRoutes.NeedChannel = api.BaseRoutes.Channels.PathPrefix("/{channel_id:[A-Za-z0-9]+}").Subrouter() - api.BaseRoutes.NeedChannelName = api.BaseRoutes.Channels.PathPrefix("/name/{channel_name:[A-Za-z0-9_-]+}").Subrouter() - api.BaseRoutes.Posts = api.BaseRoutes.NeedChannel.PathPrefix("/posts").Subrouter() - api.BaseRoutes.NeedPost = api.BaseRoutes.Posts.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter() - api.BaseRoutes.Commands = api.BaseRoutes.NeedTeam.PathPrefix("/commands").Subrouter() - api.BaseRoutes.TeamFiles = api.BaseRoutes.NeedTeam.PathPrefix("/files").Subrouter() - api.BaseRoutes.Files = api.BaseRoutes.ApiRoot.PathPrefix("/files").Subrouter() - api.BaseRoutes.NeedFile = api.BaseRoutes.Files.PathPrefix("/{file_id:[A-Za-z0-9]+}").Subrouter() - api.BaseRoutes.Hooks = api.BaseRoutes.NeedTeam.PathPrefix("/hooks").Subrouter() - api.BaseRoutes.OAuth = api.BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter() - api.BaseRoutes.Admin = api.BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter() - api.BaseRoutes.General = api.BaseRoutes.ApiRoot.PathPrefix("/general").Subrouter() - api.BaseRoutes.Preferences = api.BaseRoutes.ApiRoot.PathPrefix("/preferences").Subrouter() - api.BaseRoutes.License = api.BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter() - api.BaseRoutes.Public = api.BaseRoutes.ApiRoot.PathPrefix("/public").Subrouter() - api.BaseRoutes.Emoji = api.BaseRoutes.ApiRoot.PathPrefix("/emoji").Subrouter() - api.BaseRoutes.Webrtc = api.BaseRoutes.ApiRoot.PathPrefix("/webrtc").Subrouter() - - api.InitUser() - api.InitTeam() - api.InitChannel() - api.InitPost() - api.InitWebSocket() - api.InitFile() - api.InitCommand() - api.InitAdmin() - api.InitGeneral() - api.InitOAuth() - api.InitWebhook() - api.InitPreference() - api.InitLicense() - api.InitEmoji() - api.InitStatus() - api.InitWebrtc() - api.InitReaction() - - // 404 on any api route before web.go has a chance to serve it - root.Handle("/api/{anything:.*}", http.HandlerFunc(api.Handle404)) - - a.InitEmailBatching() - - if *a.Config().ServiceSettings.EnableAPIv3 { - mlog.Info("API version 3 is scheduled for deprecation. Please see https://api.mattermost.com for details.") - } - - return api -} - -func (api *API) Handle404(w http.ResponseWriter, r *http.Request) { - Handle404(api.App, w, r) -} - -func ReturnStatusOK(w http.ResponseWriter) { - m := make(map[string]string) - m[model.STATUS] = model.STATUS_OK - w.Write([]byte(model.MapToJson(m))) -} diff --git a/api/api_test.go b/api/api_test.go deleted file mode 100644 index a4ddf6a37..000000000 --- a/api/api_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "flag" - "os" - "testing" - - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" -) - -func TestMain(m *testing.M) { - flag.Parse() - - // Setup a global logger to catch tests logging outside of app context - // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. - mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ - EnableConsole: true, - ConsoleJson: true, - ConsoleLevel: "error", - EnableFile: false, - })) - - utils.TranslationsPreInit() - - // In the case where a dev just wants to run a single test, it's faster to just use the default - // store. - if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - mlog.Info("-test.run used, not creating temporary containers") - os.Exit(m.Run()) - } - - status := 0 - - container, settings, err := storetest.NewMySQLContainer() - if err != nil { - panic(err) - } - - UseTestStore(container, settings) - - defer func() { - StopTestStore() - os.Exit(status) - }() - - status = m.Run() -} diff --git a/api/apitestlib.go b/api/apitestlib.go deleted file mode 100644 index 20dbc4073..000000000 --- a/api/apitestlib.go +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "io" - "io/ioutil" - "net" - "os" - "strings" - "time" - - "github.com/mattermost/mattermost-server/api4" - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" - "github.com/mattermost/mattermost-server/wsapi" -) - -type TestHelper struct { - App *app.App - tempConfigPath string - - BasicClient *model.Client - BasicTeam *model.Team - BasicUser *model.User - BasicUser2 *model.User - BasicChannel *model.Channel - BasicPost *model.Post - PinnedPost *model.Post - - SystemAdminClient *model.Client - SystemAdminTeam *model.Team - SystemAdminUser *model.User - SystemAdminChannel *model.Channel -} - -type persistentTestStore struct { - store.Store -} - -func (*persistentTestStore) Close() {} - -var testStoreContainer *storetest.RunningContainer -var testStore *persistentTestStore - -// UseTestStore sets the container and corresponding settings to use for tests. Once the tests are -// complete (e.g. at the end of your TestMain implementation), you should call StopTestStore. -func UseTestStore(container *storetest.RunningContainer, settings *model.SqlSettings) { - testStoreContainer = container - testStore = &persistentTestStore{store.NewLayeredStore(sqlstore.NewSqlSupplier(*settings, nil), nil, nil)} -} - -func StopTestStore() { - if testStoreContainer != nil { - testStoreContainer.Stop() - testStoreContainer = nil - } -} - -func setupTestHelper(enterprise bool) *TestHelper { - permConfig, err := os.Open(utils.FindConfigFile("config.json")) - if err != nil { - panic(err) - } - defer permConfig.Close() - tempConfig, err := ioutil.TempFile("", "") - if err != nil { - panic(err) - } - _, err = io.Copy(tempConfig, permConfig) - tempConfig.Close() - if err != nil { - panic(err) - } - - options := []app.Option{app.ConfigFile(tempConfig.Name()), app.DisableConfigWatch} - if testStore != nil { - options = append(options, app.StoreOverride(testStore)) - } - - a, err := app.New(options...) - if err != nil { - panic(err) - } - - th := &TestHelper{ - App: a, - tempConfigPath: tempConfig.Name(), - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.MaxUsersPerTeam = 50 - *cfg.RateLimitSettings.Enable = false - cfg.EmailSettings.SendEmailNotifications = true - *cfg.ServiceSettings.EnableAPIv3 = true - }) - prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress - if testStore != nil { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) - } - serverErr := th.App.StartServer() - if serverErr != nil { - panic(serverErr) - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) - api4.Init(th.App, th.App.Srv.Router, false) - Init(th.App, th.App.Srv.Router) - wsapi.Init(th.App, th.App.Srv.WebSocketRouter) - th.App.Srv.Store.MarkSystemRanUnitTests() - th.App.DoAdvancedPermissionsMigration() - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - - if enterprise { - th.App.SetLicense(model.NewTestLicense()) - } else { - th.App.SetLicense(nil) - } - - return th -} - -func SetupEnterprise() *TestHelper { - return setupTestHelper(true) -} - -func Setup() *TestHelper { - return setupTestHelper(false) -} - -func (me *TestHelper) InitBasic() *TestHelper { - me.waitForConnectivity() - - me.BasicClient = me.CreateClient() - me.BasicUser = me.CreateUser(me.BasicClient) - me.App.UpdateUserRoles(me.BasicUser.Id, model.SYSTEM_USER_ROLE_ID, false) - me.LoginBasic() - me.BasicTeam = me.CreateTeam(me.BasicClient) - me.LinkUserToTeam(me.BasicUser, me.BasicTeam) - me.UpdateUserToNonTeamAdmin(me.BasicUser, me.BasicTeam) - me.BasicUser2 = me.CreateUser(me.BasicClient) - me.LinkUserToTeam(me.BasicUser2, me.BasicTeam) - me.BasicClient.SetTeamId(me.BasicTeam.Id) - me.BasicChannel = me.CreateChannel(me.BasicClient, me.BasicTeam) - me.BasicPost = me.CreatePost(me.BasicClient, me.BasicChannel) - - pinnedPostChannel := me.CreateChannel(me.BasicClient, me.BasicTeam) - me.PinnedPost = me.CreatePinnedPost(me.BasicClient, pinnedPostChannel) - - return me -} - -func (me *TestHelper) InitSystemAdmin() *TestHelper { - me.waitForConnectivity() - - me.SystemAdminClient = me.CreateClient() - me.SystemAdminUser = me.CreateUser(me.SystemAdminClient) - me.SystemAdminUser.Password = "Password1" - me.LoginSystemAdmin() - me.SystemAdminTeam = me.CreateTeam(me.SystemAdminClient) - me.LinkUserToTeam(me.SystemAdminUser, me.SystemAdminTeam) - me.SystemAdminClient.SetTeamId(me.SystemAdminTeam.Id) - me.App.UpdateUserRoles(me.SystemAdminUser.Id, model.SYSTEM_USER_ROLE_ID+" "+model.SYSTEM_ADMIN_ROLE_ID, false) - me.SystemAdminChannel = me.CreateChannel(me.SystemAdminClient, me.SystemAdminTeam) - - return me -} - -func (me *TestHelper) waitForConnectivity() { - for i := 0; i < 1000; i++ { - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", me.App.Srv.ListenAddr.Port)) - if err == nil { - conn.Close() - return - } - time.Sleep(time.Millisecond * 20) - } - panic("unable to connect") -} - -func (me *TestHelper) CreateClient() *model.Client { - return model.NewClient(fmt.Sprintf("http://localhost:%v", me.App.Srv.ListenAddr.Port)) -} - -func (me *TestHelper) CreateWebSocketClient() (*model.WebSocketClient, *model.AppError) { - return model.NewWebSocketClient(fmt.Sprintf("ws://localhost:%v", me.App.Srv.ListenAddr.Port), me.BasicClient.AuthToken) -} - -func (me *TestHelper) CreateTeam(client *model.Client) *model.Team { - id := model.NewId() - team := &model.Team{ - DisplayName: "dn_" + id, - Name: GenerateTestTeamName(), - Email: me.GenerateTestEmail(), - Type: model.TEAM_OPEN, - } - - utils.DisableDebugLogForTest() - r := client.Must(client.CreateTeam(team)).Data.(*model.Team) - utils.EnableDebugLogForTest() - return r -} - -func (me *TestHelper) CreateUser(client *model.Client) *model.User { - id := model.NewId() - - user := &model.User{ - Email: me.GenerateTestEmail(), - Username: "un_" + id, - Nickname: "nn_" + id, - Password: "Password1", - } - - utils.DisableDebugLogForTest() - ruser := client.Must(client.CreateUser(user, "")).Data.(*model.User) - ruser.Password = "Password1" - store.Must(me.App.Srv.Store.User().VerifyEmail(ruser.Id)) - utils.EnableDebugLogForTest() - return ruser -} - -func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { - utils.DisableDebugLogForTest() - - err := me.App.JoinUserToTeam(team, user, "") - if err != nil { - mlog.Error(err.Error()) - - time.Sleep(time.Second) - panic(err) - } - - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) { - utils.DisableDebugLogForTest() - - tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID + " " + model.TEAM_ADMIN_ROLE_ID} - if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { - utils.EnableDebugLogForTest() - mlog.Error(tmr.Err.Error()) - - time.Sleep(time.Second) - panic(tmr.Err) - } - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Team) { - utils.DisableDebugLogForTest() - - tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID} - if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { - utils.EnableDebugLogForTest() - mlog.Error(tmr.Err.Error()) - - time.Sleep(time.Second) - panic(tmr.Err) - } - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) MakeUserChannelAdmin(user *model.User, channel *model.Channel) { - utils.DisableDebugLogForTest() - - if cmr := <-me.App.Srv.Store.Channel().GetMember(channel.Id, user.Id); cmr.Err == nil { - cm := cmr.Data.(*model.ChannelMember) - cm.Roles = "channel_admin channel_user" - if sr := <-me.App.Srv.Store.Channel().UpdateMember(cm); sr.Err != nil { - utils.EnableDebugLogForTest() - panic(sr.Err) - } - } else { - utils.EnableDebugLogForTest() - panic(cmr.Err) - } - - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) MakeUserChannelUser(user *model.User, channel *model.Channel) { - utils.DisableDebugLogForTest() - - if cmr := <-me.App.Srv.Store.Channel().GetMember(channel.Id, user.Id); cmr.Err == nil { - cm := cmr.Data.(*model.ChannelMember) - cm.Roles = "channel_user" - if sr := <-me.App.Srv.Store.Channel().UpdateMember(cm); sr.Err != nil { - utils.EnableDebugLogForTest() - panic(sr.Err) - } - } else { - utils.EnableDebugLogForTest() - panic(cmr.Err) - } - - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) CreateChannel(client *model.Client, team *model.Team) *model.Channel { - return me.createChannel(client, team, model.CHANNEL_OPEN) -} - -func (me *TestHelper) CreatePrivateChannel(client *model.Client, team *model.Team) *model.Channel { - return me.createChannel(client, team, model.CHANNEL_PRIVATE) -} - -func (me *TestHelper) createChannel(client *model.Client, team *model.Team, channelType string) *model.Channel { - id := model.NewId() - - channel := &model.Channel{ - DisplayName: "dn_" + id, - Name: "name_" + id, - Type: channelType, - TeamId: team.Id, - } - - utils.DisableDebugLogForTest() - r := client.Must(client.CreateChannel(channel)).Data.(*model.Channel) - utils.EnableDebugLogForTest() - return r -} - -func (me *TestHelper) CreatePost(client *model.Client, channel *model.Channel) *model.Post { - id := model.NewId() - - post := &model.Post{ - ChannelId: channel.Id, - Message: "message_" + id, - } - - utils.DisableDebugLogForTest() - r := client.Must(client.CreatePost(post)).Data.(*model.Post) - utils.EnableDebugLogForTest() - return r -} - -func (me *TestHelper) CreatePinnedPost(client *model.Client, channel *model.Channel) *model.Post { - id := model.NewId() - - post := &model.Post{ - ChannelId: channel.Id, - Message: "message_" + id, - IsPinned: true, - } - - utils.DisableDebugLogForTest() - r := client.Must(client.CreatePost(post)).Data.(*model.Post) - utils.EnableDebugLogForTest() - return r -} - -func (me *TestHelper) LoginBasic() { - utils.DisableDebugLogForTest() - me.BasicClient.Must(me.BasicClient.Login(me.BasicUser.Email, me.BasicUser.Password)) - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) LoginBasic2() { - utils.DisableDebugLogForTest() - me.BasicClient.Must(me.BasicClient.Login(me.BasicUser2.Email, me.BasicUser2.Password)) - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) LoginSystemAdmin() { - utils.DisableDebugLogForTest() - me.SystemAdminClient.Must(me.SystemAdminClient.Login(me.SystemAdminUser.Email, me.SystemAdminUser.Password)) - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) GenerateTestEmail() string { - if me.App.Config().EmailSettings.SMTPServer != "dockerhost" && os.Getenv("CI_INBUCKET_PORT") == "" { - return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com") - } - return strings.ToLower(model.NewId() + "@dockerhost") -} - -func GenerateTestTeamName() string { - return "faketeam" + model.NewRandomString(6) -} - -func (me *TestHelper) TearDown() { - me.App.Shutdown() - os.Remove(me.tempConfigPath) - if err := recover(); err != nil { - StopTestStore() - panic(err) - } -} - -func (me *TestHelper) SaveDefaultRolePermissions() map[string][]string { - utils.DisableDebugLogForTest() - - results := make(map[string][]string) - - for _, roleName := range []string{ - "system_user", - "system_admin", - "team_user", - "team_admin", - "channel_user", - "channel_admin", - } { - role, err1 := me.App.GetRoleByName(roleName) - if err1 != nil { - utils.EnableDebugLogForTest() - panic(err1) - } - - results[roleName] = role.Permissions - } - - utils.EnableDebugLogForTest() - return results -} - -func (me *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) { - utils.DisableDebugLogForTest() - - for roleName, permissions := range data { - role, err1 := me.App.GetRoleByName(roleName) - if err1 != nil { - utils.EnableDebugLogForTest() - panic(err1) - } - - if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") { - continue - } - - role.Permissions = permissions - - _, err2 := me.App.UpdateRole(role) - if err2 != nil { - utils.EnableDebugLogForTest() - panic(err2) - } - } - - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) RemovePermissionFromRole(permission string, roleName string) { - utils.DisableDebugLogForTest() - - role, err1 := me.App.GetRoleByName(roleName) - if err1 != nil { - utils.EnableDebugLogForTest() - panic(err1) - } - - var newPermissions []string - for _, p := range role.Permissions { - if p != permission { - newPermissions = append(newPermissions, p) - } - } - - if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") { - utils.EnableDebugLogForTest() - return - } - - role.Permissions = newPermissions - - _, err2 := me.App.UpdateRole(role) - if err2 != nil { - utils.EnableDebugLogForTest() - panic(err2) - } - - utils.EnableDebugLogForTest() -} - -func (me *TestHelper) AddPermissionToRole(permission string, roleName string) { - utils.DisableDebugLogForTest() - - role, err1 := me.App.GetRoleByName(roleName) - if err1 != nil { - utils.EnableDebugLogForTest() - panic(err1) - } - - for _, existingPermission := range role.Permissions { - if existingPermission == permission { - utils.EnableDebugLogForTest() - return - } - } - - role.Permissions = append(role.Permissions, permission) - - _, err2 := me.App.UpdateRole(role) - if err2 != nil { - utils.EnableDebugLogForTest() - panic(err2) - } - - utils.EnableDebugLogForTest() -} diff --git a/api/channel.go b/api/channel.go deleted file mode 100644 index 9c465412c..000000000 --- a/api/channel.go +++ /dev/null @@ -1,843 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "net/http" - "strconv" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitChannel() { - api.BaseRoutes.Channels.Handle("/", api.ApiUserRequired(getChannels)).Methods("GET") - api.BaseRoutes.Channels.Handle("/more/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getMoreChannelsPage)).Methods("GET") - api.BaseRoutes.Channels.Handle("/more/search", api.ApiUserRequired(searchMoreChannels)).Methods("POST") - api.BaseRoutes.Channels.Handle("/counts", api.ApiUserRequired(getChannelCounts)).Methods("GET") - api.BaseRoutes.Channels.Handle("/members", api.ApiUserRequired(getMyChannelMembers)).Methods("GET") - api.BaseRoutes.Channels.Handle("/create", api.ApiUserRequired(createChannel)).Methods("POST") - api.BaseRoutes.Channels.Handle("/view", api.ApiUserRequired(viewChannel)).Methods("POST") - api.BaseRoutes.Channels.Handle("/create_direct", api.ApiUserRequired(createDirectChannel)).Methods("POST") - api.BaseRoutes.Channels.Handle("/create_group", api.ApiUserRequired(createGroupChannel)).Methods("POST") - api.BaseRoutes.Channels.Handle("/update", api.ApiUserRequired(updateChannel)).Methods("POST") - api.BaseRoutes.Channels.Handle("/update_header", api.ApiUserRequired(updateChannelHeader)).Methods("POST") - api.BaseRoutes.Channels.Handle("/update_purpose", api.ApiUserRequired(updateChannelPurpose)).Methods("POST") - api.BaseRoutes.Channels.Handle("/update_notify_props", api.ApiUserRequired(updateNotifyProps)).Methods("POST") - api.BaseRoutes.Channels.Handle("/autocomplete", api.ApiUserRequired(autocompleteChannels)).Methods("GET") - api.BaseRoutes.Channels.Handle("/name/{channel_name:[A-Za-z0-9_-]+}", api.ApiUserRequired(getChannelByName)).Methods("GET") - - api.BaseRoutes.NeedChannelName.Handle("/join", api.ApiUserRequired(join)).Methods("POST") - - api.BaseRoutes.NeedChannel.Handle("/", api.ApiUserRequired(getChannel)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/stats", api.ApiUserRequired(getChannelStats)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/members/{user_id:[A-Za-z0-9]+}", api.ApiUserRequired(getChannelMember)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/members/ids", api.ApiUserRequired(getChannelMembersByIds)).Methods("POST") - api.BaseRoutes.NeedChannel.Handle("/pinned", api.ApiUserRequired(getPinnedPosts)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/join", api.ApiUserRequired(join)).Methods("POST") - api.BaseRoutes.NeedChannel.Handle("/leave", api.ApiUserRequired(leave)).Methods("POST") - api.BaseRoutes.NeedChannel.Handle("/delete", api.ApiUserRequired(deleteChannel)).Methods("POST") - api.BaseRoutes.NeedChannel.Handle("/add", api.ApiUserRequired(addMember)).Methods("POST") - api.BaseRoutes.NeedChannel.Handle("/remove", api.ApiUserRequired(removeMember)).Methods("POST") - api.BaseRoutes.NeedChannel.Handle("/update_member_roles", api.ApiUserRequired(updateChannelMemberRoles)).Methods("POST") -} - -func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { - channel := model.ChannelFromJson(r.Body) - if channel == nil { - c.SetInvalidParam("createChannel", "channel") - return - } - - if len(channel.TeamId) == 0 { - channel.TeamId = c.TeamId - } - - if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_PUBLIC_CHANNEL) { - c.SetPermissionError(model.PERMISSION_CREATE_PUBLIC_CHANNEL) - return - } - - if channel.Type == model.CHANNEL_PRIVATE && !c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_PRIVATE_CHANNEL) { - c.SetPermissionError(model.PERMISSION_CREATE_PRIVATE_CHANNEL) - return - } - - if sc, err := c.App.CreateChannelWithUser(channel, c.Session.UserId); err != nil { - c.Err = err - return - } else { - c.LogAudit("name=" + channel.Name) - w.Write([]byte(sc.ToJson())) - } -} - -func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_CREATE_DIRECT_CHANNEL) { - c.SetPermissionError(model.PERMISSION_CREATE_DIRECT_CHANNEL) - return - } - - data := model.MapFromJson(r.Body) - - userId := data["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("createDirectChannel", "user_id") - return - } - - if sc, err := c.App.CreateDirectChannel(c.Session.UserId, userId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(sc.ToJson())) - } -} - -func createGroupChannel(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_CREATE_GROUP_CHANNEL) { - c.SetPermissionError(model.PERMISSION_CREATE_GROUP_CHANNEL) - return - } - - userIds := model.ArrayFromJson(r.Body) - if len(userIds) == 0 { - c.SetInvalidParam("createGroupChannel", "user_ids") - return - } - - found := false - for _, id := range userIds { - if id == c.Session.UserId { - found = true - break - } - } - - if !found { - userIds = append(userIds, c.Session.UserId) - } - - if sc, err := c.App.CreateGroupChannel(userIds, c.Session.UserId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(sc.ToJson())) - } -} - -func CanManageChannel(c *Context, channel *model.Channel) bool { - if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES) { - c.SetPermissionError(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES) - return false - } - - if channel.Type == model.CHANNEL_PRIVATE && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES) { - c.SetPermissionError(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES) - return false - } - - return true -} - -func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { - - channel := model.ChannelFromJson(r.Body) - - if channel == nil { - c.SetInvalidParam("updateChannel", "channel") - return - } - - var oldChannel *model.Channel - var err *model.AppError - if oldChannel, err = c.App.GetChannel(channel.Id); err != nil { - c.Err = err - return - } - - if _, err = c.App.GetChannelMember(channel.Id, c.Session.UserId); err != nil { - c.Err = err - return - } - - if !CanManageChannel(c, channel) { - return - } - - if oldChannel.DeleteAt > 0 { - c.Err = model.NewAppError("updateChannel", "api.channel.update_channel.deleted.app_error", nil, "", http.StatusBadRequest) - return - } - - if oldChannel.Name == model.DEFAULT_CHANNEL { - if (len(channel.Name) > 0 && channel.Name != oldChannel.Name) || (len(channel.Type) > 0 && channel.Type != oldChannel.Type) { - c.Err = model.NewAppError("updateChannel", "api.channel.update_channel.tried.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "", http.StatusBadRequest) - return - } - } - - oldChannel.Header = channel.Header - oldChannel.Purpose = channel.Purpose - - oldChannelDisplayName := oldChannel.DisplayName - - if len(channel.DisplayName) > 0 { - oldChannel.DisplayName = channel.DisplayName - } - - if len(channel.Name) > 0 { - oldChannel.Name = channel.Name - } - - if len(channel.Type) > 0 { - oldChannel.Type = channel.Type - } - - if _, err := c.App.UpdateChannel(oldChannel); err != nil { - c.Err = err - return - } else { - if oldChannelDisplayName != channel.DisplayName { - if err := c.App.PostUpdateChannelDisplayNameMessage(c.Session.UserId, channel, oldChannelDisplayName, channel.DisplayName); err != nil { - mlog.Error(err.Error()) - } - } - c.LogAudit("name=" + channel.Name) - w.Write([]byte(oldChannel.ToJson())) - } - -} - -func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) { - - props := model.MapFromJson(r.Body) - channelId := props["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("updateChannelHeader", "channel_id") - return - } - - channelHeader := props["channel_header"] - if len(channelHeader) > 1024 { - c.SetInvalidParam("updateChannelHeader", "channel_header") - return - } - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(channelId); err != nil { - c.Err = err - return - } - - if _, err = c.App.GetChannelMember(channelId, c.Session.UserId); err != nil { - c.Err = err - return - } - - if !CanManageChannel(c, channel) { - return - } - - oldChannelHeader := channel.Header - channel.Header = channelHeader - - if _, err := c.App.UpdateChannel(channel); err != nil { - c.Err = err - return - } else { - if err := c.App.PostUpdateChannelHeaderMessage(c.Session.UserId, channel, oldChannelHeader, channelHeader); err != nil { - mlog.Error(err.Error()) - } - c.LogAudit("name=" + channel.Name) - w.Write([]byte(channel.ToJson())) - } -} - -func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) { - - props := model.MapFromJson(r.Body) - channelId := props["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("updateChannelPurpose", "channel_id") - return - } - - channelPurpose := props["channel_purpose"] - if len(channelPurpose) > 1024 { - c.SetInvalidParam("updateChannelPurpose", "channel_purpose") - return - } - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(channelId); err != nil { - c.Err = err - return - } - - if _, err = c.App.GetChannelMember(channelId, c.Session.UserId); err != nil { - c.Err = err - return - } - - if !CanManageChannel(c, channel) { - return - } - - oldChannelPurpose := channel.Purpose - channel.Purpose = channelPurpose - - if _, err := c.App.UpdateChannel(channel); err != nil { - c.Err = err - return - } else { - if err := c.App.PostUpdateChannelPurposeMessage(c.Session.UserId, channel, oldChannelPurpose, channelPurpose); err != nil { - mlog.Error(err.Error()) - } - c.LogAudit("name=" + channel.Name) - w.Write([]byte(channel.ToJson())) - } -} - -func getChannels(c *Context, w http.ResponseWriter, r *http.Request) { - if c.TeamId == "" { - c.Err = model.NewAppError("", "api.context.missing_teamid.app_error", nil, "TeamIdRequired", http.StatusBadRequest) - return - } - // user is already in the team - // Get's all channels the user is a member of - - if channels, err := c.App.GetChannelsForUser(c.TeamId, c.Session.UserId); err != nil { - if err.Id == "store.sql_channel.get_channels.not_found.app_error" { - // lets make sure the user is valid - if _, err := c.App.GetUser(c.Session.UserId); err != nil { - c.Err = err - c.RemoveSessionCookie(w, r) - mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", c.Session.UserId), mlog.String("user_id", c.Session.UserId)) - return - } - } - c.Err = err - return - } else if c.HandleEtag(channels.Etag(), "Get Channels", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag()) - w.Write([]byte(channels.ToJson())) - } -} - -func getMoreChannelsPage(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getProfiles", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getProfiles", "limit") - return - } - - // user is already in the team - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) { - c.SetPermissionError(model.PERMISSION_LIST_TEAM_CHANNELS) - return - } - - if channels, err := c.App.GetChannelsUserNotIn(c.TeamId, c.Session.UserId, offset, limit); err != nil { - c.Err = err - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag()) - w.Write([]byte(channels.ToJson())) - } -} - -func getChannelCounts(c *Context, w http.ResponseWriter, r *http.Request) { - - // user is already in the team - - if counts, err := c.App.GetChannelCounts(c.TeamId, c.Session.UserId); err != nil { - c.Err = model.NewAppError("getChannelCounts", "api.channel.get_channel_counts.app_error", nil, err.Message, http.StatusInternalServerError) - return - } else if c.HandleEtag(counts.Etag(), "Get Channel Counts", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, counts.Etag()) - w.Write([]byte(counts.ToJson())) - } -} - -func join(c *Context, w http.ResponseWriter, r *http.Request) { - - params := mux.Vars(r) - channelId := params["channel_id"] - channelName := params["channel_name"] - - var channel *model.Channel - var err *model.AppError - if channelId != "" { - channel, err = c.App.GetChannel(channelId) - } else if channelName != "" { - channel, err = c.App.GetChannelByName(channelName, c.TeamId) - } else { - c.SetInvalidParam("join", "channel_id, channel_name") - return - } - - if err != nil { - c.Err = err - return - } - - if channel.Type == model.CHANNEL_OPEN { - if !c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) { - c.SetPermissionError(model.PERMISSION_JOIN_PUBLIC_CHANNELS) - return - } - } - - if err = c.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) { - - params := mux.Vars(r) - id := params["channel_id"] - - err := c.App.LeaveChannel(id, c.Session.UserId) - if err != nil { - c.Err = err - return - } - - result := make(map[string]string) - result["id"] = id - w.Write([]byte(model.MapToJson(result))) -} - -func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { - - params := mux.Vars(r) - id := params["channel_id"] - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(id); err != nil { - c.Err = err - return - } - - if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_DELETE_PUBLIC_CHANNEL) { - c.SetPermissionError(model.PERMISSION_DELETE_PUBLIC_CHANNEL) - return - } - - if channel.Type == model.CHANNEL_PRIVATE && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_DELETE_PRIVATE_CHANNEL) { - c.SetPermissionError(model.PERMISSION_DELETE_PRIVATE_CHANNEL) - return - } - - err = c.App.DeleteChannel(channel, c.Session.UserId) - if err != nil { - c.Err = err - return - } - - c.LogAudit("name=" + channel.Name) - - result := make(map[string]string) - result["id"] = channel.Id - w.Write([]byte(model.MapToJson(result))) -} - -func getChannel(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["channel_id"] - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(id); err != nil { - c.Err = err - return - } - - if channel.TeamId != c.TeamId && !channel.IsGroupOrDirect() { - c.Err = model.NewAppError("getChannel", "api.channel.get_channel.wrong_team.app_error", map[string]interface{}{"ChannelId": id, "TeamId": c.TeamId}, "", http.StatusBadRequest) - return - } - - var member *model.ChannelMember - if member, err = c.App.GetChannelMember(id, c.Session.UserId); err != nil { - c.Err = err - return - } - - data := &model.ChannelData{} - data.Channel = channel - data.Member = member - - if c.HandleEtag(data.Etag(), "Get Channel", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) - w.Write([]byte(data.ToJson())) - } -} - -func getChannelByName(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelName := params["channel_name"] - - if channel, err := c.App.GetChannelByName(channelName, c.TeamId); err != nil { - c.Err = err - return - } else { - if !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if channel.TeamId != c.TeamId && !channel.IsGroupOrDirect() { - c.Err = model.NewAppError("getChannel", "api.channel.get_channel.wrong_team.app_error", map[string]interface{}{"ChannelName": channelName, "TeamId": c.TeamId}, "", http.StatusBadRequest) - return - } - - if c.HandleEtag(channel.Etag(), "Get Channel By Name", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, channel.Etag()) - w.Write([]byte(channel.ToJson())) - } - } -} - -func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["channel_id"] - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(id); err != nil { - c.Err = err - return - } - - if channel.DeleteAt > 0 { - c.Err = model.NewAppError("getChannelStats", "api.channel.get_channel_extra_info.deleted.app_error", nil, "", http.StatusBadRequest) - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if memberCount, err := c.App.GetChannelMemberCount(id); err != nil { - c.Err = err - return - } else { - stats := model.ChannelStats{ChannelId: channel.Id, MemberCount: memberCount} - w.Write([]byte(stats.ToJson())) - } -} - -func getChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - userId := params["user_id"] - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if member, err := c.App.GetChannelMember(channelId, userId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(member.ToJson())) - } -} - -func getMyChannelMembers(c *Context, w http.ResponseWriter, r *http.Request) { - if members, err := c.App.GetChannelMembersForUser(c.TeamId, c.Session.UserId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(members.ToJson())) - } -} - -func getPinnedPosts(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - - if result := <-c.App.Srv.Store.Channel().GetPinnedPosts(channelId); result.Err != nil { - c.Err = result.Err - return - } else { - posts := result.Data.(*model.PostList) - w.Write([]byte(posts.ToJson())) - } - -} - -func addMember(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["channel_id"] - - data := model.MapFromJson(r.Body) - userId := data["user_id"] - - if len(userId) != 26 { - c.SetInvalidParam("addMember", "user_id") - return - } - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(id); err != nil { - c.Err = err - return - } - - if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) { - c.SetPermissionError(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) - return - } - - if channel.Type == model.CHANNEL_PRIVATE && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) { - c.SetPermissionError(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) - return - } - - var nUser *model.User - if nUser, err = c.App.GetUser(userId); err != nil { - c.Err = model.NewAppError("addMember", "api.channel.add_member.find_user.app_error", nil, err.Error(), http.StatusBadRequest) - return - } - - cm, err := c.App.AddUserToChannel(nUser, channel) - if err != nil { - c.Err = err - return - } - - c.LogAudit("name=" + channel.Name + " user_id=" + userId) - - var oUser *model.User - if oUser, err = c.App.GetUser(c.Session.UserId); err != nil { - c.Err = model.NewAppError("addMember", "api.channel.add_member.user_adding.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - - c.App.Go(func() { - c.App.PostAddToChannelMessage(oUser, nUser, channel, "") - }) - - c.App.UpdateChannelLastViewedAt([]string{id}, oUser.Id) - w.Write([]byte(cm.ToJson())) -} - -func removeMember(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - - data := model.MapFromJson(r.Body) - userIdToRemove := data["user_id"] - - if len(userIdToRemove) != 26 { - c.SetInvalidParam("removeMember", "user_id") - return - } - - var channel *model.Channel - var err *model.AppError - if channel, err = c.App.GetChannel(channelId); err != nil { - c.Err = err - return - } - - if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) { - c.SetPermissionError(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) - return - } - - if channel.Type == model.CHANNEL_PRIVATE && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) { - c.SetPermissionError(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) - return - } - - if err = c.App.RemoveUserFromChannel(userIdToRemove, c.Session.UserId, channel); err != nil { - c.Err = err - return - } - - c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove) - - result := make(map[string]string) - result["channel_id"] = channel.Id - result["removed_user_id"] = userIdToRemove - w.Write([]byte(model.MapToJson(result))) -} - -func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) { - data := model.MapFromJson(r.Body) - - userId := data["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateNotifyProps", "user_id") - return - } - - channelId := data["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("updateNotifyProps", "channel_id") - return - } - - if !c.App.SessionHasPermissionToUser(c.Session, userId) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) - return - } - - member, err := c.App.UpdateChannelMemberNotifyProps(data, channelId, userId) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.MapToJson(member.NotifyProps))) -} - -func searchMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.ChannelSearchFromJson(r.Body) - if props == nil { - c.SetInvalidParam("searchMoreChannels", "") - return - } - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if len(props.Term) == 0 { - c.SetInvalidParam("searchMoreChannels", "term") - return - } - - if channels, err := c.App.SearchChannelsUserNotIn(c.TeamId, c.Session.UserId, props.Term); err != nil { - c.Err = err - return - } else { - w.Write([]byte(channels.ToJson())) - } -} - -func autocompleteChannels(c *Context, w http.ResponseWriter, r *http.Request) { - term := r.URL.Query().Get("term") - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if channels, err := c.App.SearchChannels(c.TeamId, term); err != nil { - c.Err = err - return - } else { - w.Write([]byte(channels.ToJson())) - } - -} - -func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) { - view := model.ChannelViewFromJson(r.Body) - if view == nil { - c.SetInvalidParam("viewChannel", "channel_view") - return - } - - if _, err := c.App.ViewChannel(view, c.Session.UserId, !c.Session.IsMobileApp()); err != nil { - c.Err = err - return - } - - ReturnStatusOK(w) -} - -func getChannelMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - - userIds := model.ArrayFromJson(r.Body) - if len(userIds) == 0 { - c.SetInvalidParam("getChannelMembersByIds", "user_ids") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if members, err := c.App.GetChannelMembersByIds(channelId, userIds); err != nil { - c.Err = err - return - } else { - w.Write([]byte(members.ToJson())) - } -} - -func updateChannelMemberRoles(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - - props := model.MapFromJson(r.Body) - - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateChannelMemberRoles", "user_id") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_MANAGE_CHANNEL_ROLES) { - c.SetPermissionError(model.PERMISSION_MANAGE_CHANNEL_ROLES) - return - } - - newRoles := props["new_roles"] - if !(model.IsValidUserRoles(newRoles)) { - c.SetInvalidParam("updateChannelMemberRoles", "new_roles") - return - } - - if _, err := c.App.UpdateChannelMemberRoles(channelId, userId, newRoles); err != nil { - c.Err = err - return - } - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} diff --git a/api/channel_test.go b/api/channel_test.go deleted file mode 100644 index 2642eb9ff..000000000 --- a/api/channel_test.go +++ /dev/null @@ -1,2007 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" -) - -func TestCreateChannel(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - th.LoginBasic2() - team2 := th.CreateTeam(th.BasicClient) - th.LoginBasic() - th.BasicClient.SetTeamId(team.Id) - - channel := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - rchannel, err := Client.CreateChannel(&channel) - if err != nil { - t.Fatal(err) - } - - if rchannel.Data.(*model.Channel).Name != channel.Name { - t.Fatal("full name didn't match") - } - - rget := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - nameMatch := false - for _, c := range *rget { - if c.Name == channel.Name { - nameMatch = true - } - } - - if !nameMatch { - t.Fatal("Did not create channel with correct name") - } - - if _, err := Client.CreateChannel(rchannel.Data.(*model.Channel)); err == nil { - t.Fatal("Cannot create an existing") - } - - savedId := rchannel.Data.(*model.Channel).Id - - rchannel.Data.(*model.Channel).Id = "" - if _, err := Client.CreateChannel(rchannel.Data.(*model.Channel)); err != nil { - if err.Id != "store.sql_channel.save_channel.exists.app_error" { - t.Fatal(err) - } - } - - if _, err := Client.DoApiPost(Client.GetTeamRoute()+"/channels/create", "garbage"); err == nil { - t.Fatal("should have been an error") - } - - Client.DeleteChannel(savedId) - if _, err := Client.CreateChannel(rchannel.Data.(*model.Channel)); err != nil { - if err.Message != "A channel with that URL was previously created" { - t.Fatal(err) - } - } - - channel = model.Channel{DisplayName: "Channel on Different Team", Name: "aaaa" + model.NewId() + "abbb", Type: model.CHANNEL_OPEN, TeamId: team2.Id} - - if _, err := Client.CreateChannel(&channel); err.StatusCode != http.StatusForbidden { - t.Fatal(err) - } - - channel = model.Channel{DisplayName: "Channel With No TeamId", Name: "aaaa" + model.NewId() + "abbb", Type: model.CHANNEL_OPEN, TeamId: ""} - - if _, err := Client.CreateChannel(&channel); err != nil { - t.Fatal(err) - } - - channel = model.Channel{DisplayName: "Test API Name", Name: model.NewId() + "__" + model.NewId(), Type: model.CHANNEL_OPEN, TeamId: team.Id} - - if _, err := Client.CreateChannel(&channel); err == nil { - t.Fatal("Should have errored out on invalid '__' character") - } - - channel = model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_DIRECT, TeamId: team.Id} - - if _, err := Client.CreateChannel(&channel); err == nil { - t.Fatal("Should have errored out on direct channel type") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.AddPermissionToRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - - channel2 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel3 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - if _, err := Client.CreateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.CreateChannel(channel3); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_ADMIN_ROLE_ID) - - th.LoginBasic2() - channel2.Name = "zz" + model.NewId() + "a" - channel3.Name = "zz" + model.NewId() + "a" - if _, err := Client.CreateChannel(channel2); err == nil { - t.Fatal("should have errored not team admin") - } - if _, err := Client.CreateChannel(channel3); err == nil { - t.Fatal("should have errored not team admin") - } - - th.UpdateUserToTeamAdmin(th.BasicUser, team) - Client.Logout() - th.LoginBasic() - Client.SetTeamId(team.Id) - - if _, err := Client.CreateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.CreateChannel(channel3); err != nil { - t.Fatal(err) - } -} - -func TestCreateDirectChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user := th.BasicUser - user2 := th.BasicUser2 - - var channel *model.Channel - if result, err := Client.CreateDirectChannel(th.BasicUser2.Id); err != nil { - t.Fatal(err) - } else { - channel = result.Data.(*model.Channel) - } - - channelName := "" - if user2.Id > user.Id { - channelName = user.Id + "__" + user2.Id - } else { - channelName = user2.Id + "__" + user.Id - } - - if channel.Name != channelName { - t.Fatal("channel name didn't match") - } - - if channel.Type != model.CHANNEL_DIRECT { - t.Fatal("channel type was not direct") - } - - // Don't fail on direct channels already existing and return the original channel again - if result, err := Client.CreateDirectChannel(th.BasicUser2.Id); err != nil { - t.Fatal(err) - } else if result.Data.(*model.Channel).Id != channel.Id { - t.Fatal("didn't return original direct channel when saving a duplicate") - } - - if _, err := Client.CreateDirectChannel("junk"); err == nil { - t.Fatal("should have failed with bad user id") - } - - if _, err := Client.CreateDirectChannel("12345678901234567890123456"); err == nil { - t.Fatal("should have failed with non-existent user") - } -} - -func TestCreateGroupChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user := th.BasicUser - user2 := th.BasicUser2 - user3 := th.CreateUser(Client) - - userIds := []string{user.Id, user2.Id, user3.Id} - - var channel *model.Channel - if result, err := Client.CreateGroupChannel(userIds); err != nil { - t.Fatal(err) - } else { - channel = result.Data.(*model.Channel) - } - - if channel.Type != model.CHANNEL_GROUP { - t.Fatal("channel type was not group") - } - - // Don't fail on group channels already existing and return the original channel again - if result, err := Client.CreateGroupChannel(userIds); err != nil { - t.Fatal(err) - } else if result.Data.(*model.Channel).Id != channel.Id { - t.Fatal("didn't return original group channel when saving a duplicate") - } - - if _, err := Client.CreateGroupChannel([]string{user.Id}); err == nil { - t.Fatal("should have failed with not enough users") - } - - if _, err := Client.CreateGroupChannel([]string{}); err == nil { - t.Fatal("should have failed with not enough users") - } - - if _, err := Client.CreateGroupChannel([]string{user.Id, user2.Id, user3.Id, "junk"}); err == nil { - t.Fatal("should have failed with non-existent user") - } -} - -func TestUpdateChannel(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - sysAdminUser := th.SystemAdminUser - user := th.CreateUser(Client) - th.LinkUserToTeam(user, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - Client.Login(user.Email, user.Password) - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - Client.AddChannelMember(channel1.Id, user.Id) - - header := "zz" + model.NewId() + "a" - purpose := "zz" + model.NewId() + "a" - upChannel1 := &model.Channel{Id: channel1.Id, Header: header, Purpose: purpose} - upChannel1 = Client.Must(Client.UpdateChannel(upChannel1)).Data.(*model.Channel) - - if upChannel1.Header != header { - t.Fatal("Channel admin failed to update header") - } - - if upChannel1.Purpose != purpose { - t.Fatal("Channel admin failed to update purpose") - } - - if upChannel1.DisplayName != channel1.DisplayName { - t.Fatal("Channel admin failed to skip displayName") - } - - rget := Client.Must(Client.GetChannels("")) - channels := rget.Data.(*model.ChannelList) - for _, c := range *channels { - if c.Name == model.DEFAULT_CHANNEL { - c.Header = "new header" - c.Name = "pseudo-square" - if _, err := Client.UpdateChannel(c); err == nil { - t.Fatal("should have errored on updating default channel name") - } - break - } - } - - Client.Login(user2.Email, user2.Password) - - if _, err := Client.UpdateChannel(upChannel1); err == nil { - t.Fatal("Standard User should have failed to update") - } - - Client.Must(Client.JoinChannel(channel1.Id)) - th.UpdateUserToTeamAdmin(user2, team) - - Client.Logout() - Client.Login(user2.Email, user2.Password) - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateChannel(upChannel1); err != nil { - t.Fatal(err) - } - - Client.Login(sysAdminUser.Email, sysAdminUser.Password) - th.LinkUserToTeam(sysAdminUser, team) - Client.Must(Client.JoinChannel(channel1.Id)) - - if _, err := Client.UpdateChannel(upChannel1); err != nil { - t.Fatal(err) - } - - Client.Must(Client.DeleteChannel(channel1.Id)) - - if _, err := Client.UpdateChannel(upChannel1); err == nil { - t.Fatal("should have failed - channel deleted") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) - - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - - channel2 := th.CreateChannel(Client, team) - channel3 := th.CreatePrivateChannel(Client, team) - - th.LinkUserToTeam(th.BasicUser, team) - - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.UpdateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannel(channel3); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) - - th.MakeUserChannelUser(th.BasicUser, channel2) - th.MakeUserChannelUser(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.UpdateChannel(channel2); err == nil { - t.Fatal("should have errored not channel admin") - } - if _, err := Client.UpdateChannel(channel3); err == nil { - t.Fatal("should have errored not channel admin") - } - - th.MakeUserChannelAdmin(th.BasicUser, channel2) - th.MakeUserChannelAdmin(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.UpdateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannel(channel3); err != nil { - t.Fatal(err) - } -} - -func TestUpdateChannelDisplayName(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - user := th.CreateUser(Client) - th.LinkUserToTeam(user, team) - - Client.Login(user.Email, user.Password) - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - Client.AddChannelMember(channel1.Id, user.Id) - - newDisplayName := "a" + channel1.DisplayName + "a" - channel1.DisplayName = newDisplayName - channel1 = Client.Must(Client.UpdateChannel(channel1)).Data.(*model.Channel) - - time.Sleep(100 * time.Millisecond) - - r1 := Client.Must(Client.GetPosts(channel1.Id, 0, 1, "")).Data.(*model.PostList) - if len(r1.Order) != 1 { - t.Fatal("Displayname update system message was not found") - } -} - -func TestUpdateChannelHeader(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - data := make(map[string]string) - data["channel_id"] = channel1.Id - data["channel_header"] = "new header" - - var upChannel1 *model.Channel - if result, err := Client.UpdateChannelHeader(data); err != nil { - t.Fatal(err) - } else { - upChannel1 = result.Data.(*model.Channel) - } - - time.Sleep(100 * time.Millisecond) - - r1 := Client.Must(Client.GetPosts(channel1.Id, 0, 1, "")).Data.(*model.PostList) - if len(r1.Order) != 1 { - t.Fatal("Header update system message was not found") - } else if val, ok := r1.Posts[r1.Order[0]].Props["old_header"]; !ok || val != "" { - t.Fatal("Props should contain old_header with old header value") - } else if val, ok := r1.Posts[r1.Order[0]].Props["new_header"]; !ok || val != "new header" { - t.Fatal("Props should contain new_header with new header value") - } - - if upChannel1.Header != data["channel_header"] { - t.Fatal("Failed to update header") - } - - data["channel_id"] = "junk" - if _, err := Client.UpdateChannelHeader(data); err == nil { - t.Fatal("should have errored on junk channel id") - } - - data["channel_id"] = "12345678901234567890123456" - if _, err := Client.UpdateChannelHeader(data); err == nil { - t.Fatal("should have errored on non-existent channel id") - } - - data["channel_id"] = channel1.Id - data["channel_header"] = strings.Repeat("a", 1050) - if _, err := Client.UpdateChannelHeader(data); err == nil { - t.Fatal("should have errored on bad channel header") - } - - rchannel := Client.Must(Client.CreateDirectChannel(th.BasicUser2.Id)).Data.(*model.Channel) - data["channel_id"] = rchannel.Id - data["channel_header"] = "new header" - var upChanneld *model.Channel - if result, err := Client.UpdateChannelHeader(data); err != nil { - t.Fatal(err) - } else { - upChanneld = result.Data.(*model.Channel) - } - - if upChanneld.Header != data["channel_header"] { - t.Fatal("Failed to update header") - } - - th.LoginBasic2() - - data["channel_id"] = channel1.Id - data["channel_header"] = "new header" - if _, err := Client.UpdateChannelHeader(data); err == nil { - t.Fatal("should have errored non-channel member trying to update header") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) - - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - - th.LoginBasic() - channel2 := th.CreateChannel(Client, team) - channel3 := th.CreatePrivateChannel(Client, team) - - data2 := make(map[string]string) - data2["channel_id"] = channel2.Id - data2["channel_header"] = "new header" - - data3 := make(map[string]string) - data3["channel_id"] = channel3.Id - data3["channel_header"] = "new header" - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.UpdateChannelHeader(data2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannelHeader(data3); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) - - th.MakeUserChannelUser(th.BasicUser, channel2) - th.MakeUserChannelUser(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.UpdateChannelHeader(data2); err == nil { - t.Fatal("should have errored not channel admin") - } - if _, err := Client.UpdateChannelHeader(data3); err == nil { - t.Fatal("should have errored not channel admin") - } - - th.MakeUserChannelAdmin(th.BasicUser, channel2) - th.MakeUserChannelAdmin(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.UpdateChannelHeader(data2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannelHeader(data3); err != nil { - t.Fatal(err) - } -} - -func TestUpdateChannelPurpose(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - data := make(map[string]string) - data["channel_id"] = channel1.Id - data["channel_purpose"] = "new purpose" - - var upChannel1 *model.Channel - if result, err := Client.UpdateChannelPurpose(data); err != nil { - t.Fatal(err) - } else { - upChannel1 = result.Data.(*model.Channel) - } - - time.Sleep(100 * time.Millisecond) - - r1 := Client.Must(Client.GetPosts(channel1.Id, 0, 1, "")).Data.(*model.PostList) - if len(r1.Order) != 1 { - t.Fatal("Purpose update system message was not found") - } else if val, ok := r1.Posts[r1.Order[0]].Props["old_purpose"]; !ok || val != "" { - t.Fatal("Props should contain old_header with old purpose value") - } else if val, ok := r1.Posts[r1.Order[0]].Props["new_purpose"]; !ok || val != "new purpose" { - t.Fatal("Props should contain new_header with new purpose value") - } - - if upChannel1.Purpose != data["channel_purpose"] { - t.Fatal("Failed to update purpose") - } - - data["channel_id"] = "junk" - if _, err := Client.UpdateChannelPurpose(data); err == nil { - t.Fatal("should have errored on junk channel id") - } - - data["channel_id"] = "12345678901234567890123456" - if _, err := Client.UpdateChannelPurpose(data); err == nil { - t.Fatal("should have errored on non-existent channel id") - } - - data["channel_id"] = channel1.Id - data["channel_purpose"] = strings.Repeat("a", 350) - if _, err := Client.UpdateChannelPurpose(data); err == nil { - t.Fatal("should have errored on bad channel purpose") - } - - th.LoginBasic2() - - data["channel_id"] = channel1.Id - data["channel_purpose"] = "new purpose" - if _, err := Client.UpdateChannelPurpose(data); err == nil { - t.Fatal("should have errored non-channel member trying to update purpose") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) - - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - - th.LoginBasic() - channel2 := th.CreateChannel(Client, team) - channel3 := th.CreatePrivateChannel(Client, team) - - data2 := make(map[string]string) - data2["channel_id"] = channel2.Id - data2["channel_purpose"] = "new purpose" - - data3 := make(map[string]string) - data3["channel_id"] = channel3.Id - data3["channel_purpose"] = "new purpose" - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.UpdateChannelPurpose(data2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannelPurpose(data3); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) - - th.MakeUserChannelUser(th.BasicUser, channel2) - th.MakeUserChannelUser(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.UpdateChannelPurpose(data2); err == nil { - t.Fatal("should have errored not channel admin") - } - if _, err := Client.UpdateChannelPurpose(data3); err == nil { - t.Fatal("should have errored not channel admin") - } - - th.MakeUserChannelAdmin(th.BasicUser, channel2) - th.MakeUserChannelAdmin(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.UpdateChannelPurpose(data2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannelPurpose(data3); err != nil { - t.Fatal(err) - } -} - -func TestGetChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - team2 := th.CreateTeam(Client) - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - rget := Client.Must(Client.GetChannels("")) - channels := rget.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - - if (*channels)[1].DisplayName != channel2.DisplayName { - t.Fatal("full name didn't match") - } - - // test etag caching - if cache_result, err := Client.GetChannels(rget.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(*model.ChannelList) != nil { - t.Log(cache_result.Data) - t.Fatal("cache should be empty") - } - - view := model.ChannelView{ChannelId: channel2.Id, PrevChannelId: channel1.Id} - if _, resp := Client.ViewChannel(view); resp.Error != nil { - t.Fatal(resp.Error) - } - - if resp, err := Client.GetChannel(channel1.Id, ""); err != nil { - t.Fatal(err) - } else { - data := resp.Data.(*model.ChannelData) - if data.Channel.DisplayName != channel1.DisplayName { - t.Fatal("name didn't match") - } - - // test etag caching - if cache_result, err := Client.GetChannel(channel1.Id, resp.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(*model.ChannelData) != nil { - t.Log(cache_result.Data) - t.Fatal("cache should be empty") - } - } - - if _, err := Client.GetChannel("junk", ""); err == nil { - t.Fatal("should have failed - bad channel id") - } - - th.BasicClient.SetTeamId(team2.Id) - if _, err := Client.GetChannel(channel2.Id, ""); err == nil { - t.Fatal("should have failed - wrong team") - } - - //Test if a wrong team id is supplied should return error - if _, err := Client.CreateDirectChannel(th.BasicUser2.Id); err != nil { - t.Fatal(err) - } - - th.BasicClient.SetTeamId("nonexitingteamid") - if _, err := Client.GetChannels(""); err == nil { - t.Fatal("should have failed - wrong team id") - } -} - -func TestGetMoreChannelsPage(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "b" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - channel3 := &model.Channel{DisplayName: "C Test API Name", Name: "c" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - - th.LoginBasic2() - - if r, err := Client.GetMoreChannelsPage(0, 100); err != nil { - t.Fatal(err) - } else { - channels := r.Data.(*model.ChannelList) - - // 1 for BasicChannel, 1 for PinnedPostChannel, 2 for open channels created above - if len(*channels) != 4 { - t.Fatal("wrong length") - } - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - - if (*channels)[1].DisplayName != channel2.DisplayName { - t.Fatal("full name didn't match") - } - } - - if r, err := Client.GetMoreChannelsPage(0, 1); err != nil { - t.Fatal(err) - } else { - channels := r.Data.(*model.ChannelList) - - if len(*channels) != 1 { - t.Fatal("wrong length") - } - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - } - - if r, err := Client.GetMoreChannelsPage(1, 1); err != nil { - t.Fatal(err) - } else { - channels := r.Data.(*model.ChannelList) - - if len(*channels) != 1 { - t.Fatal("wrong length") - } - - if (*channels)[0].DisplayName != channel2.DisplayName { - t.Fatal("full name didn't match") - } - } - - Client.SetTeamId("junk") - if _, err := Client.GetMoreChannelsPage(0, 1); err == nil { - t.Fatal("should have failed - bad team id") - } -} - -func TestGetChannelCounts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - if result, err := Client.GetChannelCounts(""); err != nil { - t.Fatal(err) - } else { - counts := result.Data.(*model.ChannelCounts) - - if len(counts.Counts) != 6 { - t.Fatal("wrong number of channel counts") - } - - if len(counts.UpdateTimes) != 6 { - t.Fatal("wrong number of channel update times") - } - - if cache_result, err := Client.GetChannelCounts(result.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(*model.ChannelCounts) != nil { - t.Log(cache_result.Data) - t.Fatal("result data should be empty") - } - } - -} - -func TestGetMyChannelMembers(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - if result, err := Client.GetMyChannelMembers(); err != nil { - t.Fatal(err) - } else { - members := result.Data.(*model.ChannelMembers) - - // town-square, off-topic, basic test channel, pinned post channel, channel1, channel2 - if len(*members) != 6 { - t.Fatal("wrong number of members", len(*members)) - } - } - -} - -func TestJoinChannelById(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - - th.LoginBasic2() - - Client.Must(Client.JoinChannel(channel1.Id)) - - if _, err := Client.JoinChannel(channel3.Id); err == nil { - t.Fatal("shouldn't be able to join secret group") - } - - rchannel := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id)).Data.(*model.Channel) - - user3 := th.CreateUser(th.BasicClient) - th.LinkUserToTeam(user3, team) - Client.Must(Client.Login(user3.Email, "Password1")) - - if _, err := Client.JoinChannel(rchannel.Id); err == nil { - t.Fatal("shoudn't be able to join direct channel") - } - - th.LoginBasic() - - if _, err := Client.JoinChannel(channel1.Id); err != nil { - t.Fatal("should be able to join public channel that we're a member of") - } - - if _, err := Client.JoinChannel(channel3.Id); err != nil { - t.Fatal("should be able to join private channel that we're a member of") - } - - if _, err := Client.JoinChannel(rchannel.Id); err != nil { - t.Fatal("should be able to join direct channel that we're a member of") - } -} - -func TestJoinChannelByName(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - - th.LoginBasic2() - - Client.Must(Client.JoinChannelByName(channel1.Name)) - - if _, err := Client.JoinChannelByName(channel3.Name); err == nil { - t.Fatal("shouldn't be able to join secret group") - } - - rchannel := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id)).Data.(*model.Channel) - - user3 := th.CreateUser(th.BasicClient) - th.LinkUserToTeam(user3, team) - Client.Must(Client.Login(user3.Email, "Password1")) - - if _, err := Client.JoinChannelByName(rchannel.Name); err == nil { - t.Fatal("shoudn't be able to join direct channel") - } - - th.LoginBasic() - - if _, err := Client.JoinChannelByName(channel1.Name); err != nil { - t.Fatal("should be able to join public channel that we're a member of") - } - - if _, err := Client.JoinChannelByName(channel3.Name); err != nil { - t.Fatal("should be able to join private channel that we're a member of") - } - - if _, err := Client.JoinChannelByName(rchannel.Name); err != nil { - t.Fatal("should be able to join direct channel that we're a member of") - } -} - -func TestJoinChannelByNameDisabledUser(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - Client.Must(th.BasicClient.RemoveUserFromTeam(th.BasicTeam.Id, th.BasicUser.Id)) - - if _, err := th.App.AddUserToChannel(th.BasicUser, channel1); err == nil { - t.Fatal("shoudn't be able to join channel") - } else { - if err.Id != "api.channel.add_user.to.channel.failed.deleted.app_error" { - t.Fatal("wrong error") - } - } -} - -func TestLeaveChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - - th.LoginBasic2() - - Client.Must(Client.JoinChannel(channel1.Id)) - - // Cannot leave a the private group if you are the only member - if _, err := Client.LeaveChannel(channel3.Id); err == nil { - t.Fatal("should have errored, cannot leave private group if only one member") - } - - rchannel := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id)).Data.(*model.Channel) - - if _, err := Client.LeaveChannel(rchannel.Id); err == nil { - t.Fatal("should have errored, cannot leave direct channel") - } - - rget := Client.Must(Client.GetChannels("")) - cdata := rget.Data.(*model.ChannelList) - for _, c := range *cdata { - if c.Name == model.DEFAULT_CHANNEL { - if _, err := Client.LeaveChannel(c.Id); err == nil { - t.Fatal("should have errored on leaving default channel") - } - break - } - } -} - -func TestDeleteChannel(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - userSystemAdmin := th.SystemAdminUser - userTeamAdmin := th.CreateUser(Client) - th.LinkUserToTeam(userTeamAdmin, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - Client.Login(user2.Email, user2.Password) - - channelMadeByCA := &model.Channel{DisplayName: "C Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channelMadeByCA = Client.Must(Client.CreateChannel(channelMadeByCA)).Data.(*model.Channel) - - Client.AddChannelMember(channelMadeByCA.Id, userTeamAdmin.Id) - - Client.Login(userTeamAdmin.Email, "pwd") - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - if _, err := Client.DeleteChannel(channel1.Id); err != nil { - t.Fatal(err) - } - - if _, err := Client.DeleteChannel(channelMadeByCA.Id); err != nil { - t.Fatal("Team admin failed to delete Channel Admin's channel") - } - - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - if _, err := Client.CreatePost(post1); err == nil { - t.Fatal("should have failed to post to deleted channel") - } - - userStd := th.CreateUser(Client) - th.LinkUserToTeam(userStd, team) - Client.Login(userStd.Email, userStd.Password) - - if _, err := Client.JoinChannel(channel1.Id); err == nil { - t.Fatal("should have failed to join deleted channel") - } - - Client.Must(Client.JoinChannel(channel2.Id)) - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - - rget := Client.Must(Client.GetChannels("")) - cdata := rget.Data.(*model.ChannelList) - for _, c := range *cdata { - if c.Name == model.DEFAULT_CHANNEL { - if _, err := Client.DeleteChannel(c.Id); err == nil { - t.Fatal("should have errored on deleting default channel") - } - break - } - } - - th.UpdateUserToTeamAdmin(userStd, team) - - Client.Logout() - Client.Login(userStd.Email, userStd.Password) - Client.SetTeamId(team.Id) - - channel2 = th.CreateChannel(Client, team) - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - - channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - - Client.Login(userSystemAdmin.Email, userSystemAdmin.Password) - Client.Must(Client.JoinChannel(channel3.Id)) - - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } - - if _, err := Client.DeleteChannel(channel3.Id); err == nil { - t.Fatal("should have failed - channel already deleted") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - - th.LoginSystemAdmin() - th.LinkUserToTeam(th.BasicUser, team) - - channel2 = th.CreateChannel(Client, team) - channel3 = th.CreatePrivateChannel(Client, team) - channel4 := th.CreateChannel(Client, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel4.Id, th.BasicUser.Id)) - Client.Must(Client.LeaveChannel(channel4.Id)) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) - - th.LoginSystemAdmin() - - channel2 = th.CreateChannel(Client, team) - channel3 = th.CreatePrivateChannel(Client, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.DeleteChannel(channel2.Id); err == nil { - t.Fatal("should have errored not channel admin") - } - if _, err := Client.DeleteChannel(channel3.Id); err == nil { - t.Fatal("should have errored not channel admin") - } - - th.MakeUserChannelAdmin(th.BasicUser, channel2) - th.MakeUserChannelAdmin(th.BasicUser, channel3) - th.App.Srv.Store.Channel().ClearCaches() - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } - - th.LoginSystemAdmin() - - channel2 = th.CreateChannel(Client, team) - channel3 = th.CreatePrivateChannel(Client, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.DeleteChannel(channel2.Id); err == nil { - t.Fatal("should have errored not system admin") - } - if _, err := Client.DeleteChannel(channel3.Id); err == nil { - t.Fatal("should have errored not system admin") - } - - if _, err := Client.DeleteChannel(channel4.Id); err == nil { - t.Fatal("Should not be able to delete channel, even though only one user is left") - } - - th.LoginSystemAdmin() - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } -} - -func TestGetChannelStats(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - rget := Client.Must(Client.GetChannelStats(channel1.Id, "")) - data := rget.Data.(*model.ChannelStats) - if data.ChannelId != channel1.Id { - t.Fatal("couldnt't get extra info") - } else if data.MemberCount != 1 { - t.Fatal("got incorrect member count") - } -} - -func TestAddChannelMember(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user1 := th.BasicUser - user2 := th.BasicUser2 - user3 := th.CreateUser(Client) - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - if _, err := Client.AddChannelMember(channel1.Id, user2.Id); err != nil { - t.Fatal(err) - } - - if _, err := Client.AddChannelMember(channel1.Id, "dsgsdg"); err == nil { - t.Fatal("Should have errored, bad user id") - } - - if _, err := Client.AddChannelMember(channel1.Id, "12345678901234567890123456"); err == nil { - t.Fatal("Should have errored, bad user id") - } - - if _, err := Client.AddChannelMember(channel1.Id, user2.Id); err != nil { - t.Fatal(err) - } - - if _, err := Client.AddChannelMember("sgdsgsdg", user2.Id); err == nil { - t.Fatal("Should have errored, bad channel id") - } - - channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - th.LoginBasic2() - - if _, err := Client.AddChannelMember(channel2.Id, user2.Id); err == nil { - t.Fatal("Should have errored, user not in channel") - } - - th.LoginBasic() - - Client.Must(Client.DeleteChannel(channel2.Id)) - - if _, err := Client.AddChannelMember(channel2.Id, user2.Id); err == nil { - t.Fatal("Should have errored, channel deleted") - } - - if _, err := Client.AddChannelMember(channel1.Id, user3.Id); err == nil { - t.Fatal("Should have errored, user not on team") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - - // Check that a regular channel user can add other users. - channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel4 = Client.Must(th.SystemAdminClient.CreateChannel(channel4)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel4.Id, user1.Id)) - if _, err := Client.AddChannelMember(channel4.Id, user2.Id); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) - - channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel5.Id, user1.Id)) - if _, err := Client.AddChannelMember(channel5.Id, user2.Id); err == nil { - t.Fatal("Should have failed due to permissions") - } - - th.MakeUserChannelAdmin(user1, channel5) - th.App.InvalidateAllCaches() - - if _, err := Client.AddChannelMember(channel5.Id, user2.Id); err != nil { - t.Fatal(err) - } -} - -func TestRemoveChannelMember(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user1 := th.BasicUser - user2 := th.BasicUser2 - th.UpdateUserToTeamAdmin(user2, team) - - channelMadeByCA := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channelMadeByCA = Client.Must(Client.CreateChannel(channelMadeByCA)).Data.(*model.Channel) - - Client.Must(Client.AddChannelMember(channelMadeByCA.Id, user2.Id)) - - th.LoginBasic2() - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - userStd := th.CreateUser(th.BasicClient) - th.LinkUserToTeam(userStd, team) - - Client.Must(Client.AddChannelMember(channel1.Id, userStd.Id)) - - Client.Must(Client.AddChannelMember(channelMadeByCA.Id, userStd.Id)) - - if _, err := Client.RemoveChannelMember(channel1.Id, "dsgsdg"); err == nil { - t.Fatal("Should have errored, bad user id") - } - - if _, err := Client.RemoveChannelMember("sgdsgsdg", userStd.Id); err == nil { - t.Fatal("Should have errored, bad channel id") - } - - if _, err := Client.RemoveChannelMember(channel1.Id, userStd.Id); err != nil { - t.Fatal(err) - } - - if _, err := Client.RemoveChannelMember(channelMadeByCA.Id, userStd.Id); err != nil { - t.Fatal("Team Admin failed to remove member from Channel Admin's channel") - } - - channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - Client.Login(userStd.Email, userStd.Password) - - if _, err := Client.RemoveChannelMember(channel2.Id, userStd.Id); err == nil { - t.Fatal("Should have errored, user not channel admin") - } - - th.LoginBasic2() - Client.Must(Client.AddChannelMember(channel2.Id, userStd.Id)) - - Client.Must(Client.DeleteChannel(channel2.Id)) - - if _, err := Client.RemoveChannelMember(channel2.Id, userStd.Id); err == nil { - t.Fatal("Should have errored, channel deleted") - } - - townSquare := Client.Must(Client.GetChannelByName("town-square")).Data.(*model.Channel) - - if _, err := Client.RemoveChannelMember(townSquare.Id, userStd.Id); err == nil { - t.Fatal("should have errored, channel is default") - } - - th.LoginBasic() - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - - // Check that a regular channel user can remove other users. - channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel4 = Client.Must(th.SystemAdminClient.CreateChannel(channel4)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel4.Id, user1.Id)) - Client.Must(th.SystemAdminClient.AddChannelMember(channel4.Id, user2.Id)) - if _, err := Client.RemoveChannelMember(channel4.Id, user2.Id); err != nil { - t.Fatal(err) - } - - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) - - channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel5.Id, user1.Id)) - Client.Must(th.SystemAdminClient.AddChannelMember(channel5.Id, user2.Id)) - if _, err := Client.RemoveChannelMember(channel5.Id, user2.Id); err == nil { - t.Fatal("Should have failed due to permissions") - } - - th.MakeUserChannelAdmin(user1, channel5) - th.App.InvalidateAllCaches() - - if _, err := Client.RemoveChannelMember(channel5.Id, user2.Id); err != nil { - t.Fatal(err) - } -} - -func TestUpdateNotifyProps(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user := th.BasicUser - user2 := th.BasicUser2 - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - data := make(map[string]string) - data["channel_id"] = channel1.Id - data["user_id"] = user.Id - data[model.DESKTOP_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION - - //timeBeforeUpdate := model.GetMillis() - time.Sleep(100 * time.Millisecond) - - // test updating desktop - if result, err := Client.UpdateNotifyProps(data); err != nil { - t.Fatal(err) - } else if notifyProps := result.Data.(map[string]string); notifyProps[model.DESKTOP_NOTIFY_PROP] != model.CHANNEL_NOTIFY_MENTION { - t.Fatal("NotifyProps[\"desktop\"] did not update properly") - } else if notifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.CHANNEL_MARK_UNREAD_ALL { - t.Fatalf("NotifyProps[\"mark_unread\"] changed to %v", notifyProps[model.MARK_UNREAD_NOTIFY_PROP]) - } - - // test an empty update - delete(data, model.DESKTOP_NOTIFY_PROP) - - if result, err := Client.UpdateNotifyProps(data); err != nil { - t.Fatal(err) - } else if notifyProps := result.Data.(map[string]string); notifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.CHANNEL_MARK_UNREAD_ALL { - t.Fatalf("NotifyProps[\"mark_unread\"] changed to %v", notifyProps[model.MARK_UNREAD_NOTIFY_PROP]) - } else if notifyProps[model.DESKTOP_NOTIFY_PROP] != model.CHANNEL_NOTIFY_MENTION { - t.Fatalf("NotifyProps[\"desktop\"] changed to %v", notifyProps[model.DESKTOP_NOTIFY_PROP]) - } - - // test updating mark unread - data[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION - - if result, err := Client.UpdateNotifyProps(data); err != nil { - t.Fatal(err) - } else if notifyProps := result.Data.(map[string]string); notifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.CHANNEL_MARK_UNREAD_MENTION { - t.Fatal("NotifyProps[\"mark_unread\"] did not update properly") - } else if notifyProps[model.DESKTOP_NOTIFY_PROP] != model.CHANNEL_NOTIFY_MENTION { - t.Fatalf("NotifyProps[\"desktop\"] changed to %v", notifyProps[model.DESKTOP_NOTIFY_PROP]) - } - - // test updating both - data[model.DESKTOP_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE - data[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION - - if result, err := Client.UpdateNotifyProps(data); err != nil { - t.Fatal(err) - } else if notifyProps := result.Data.(map[string]string); notifyProps[model.DESKTOP_NOTIFY_PROP] != model.CHANNEL_NOTIFY_NONE { - t.Fatal("NotifyProps[\"desktop\"] did not update properly") - } else if notifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.CHANNEL_MARK_UNREAD_MENTION { - t.Fatal("NotifyProps[\"mark_unread\"] did not update properly") - } - - // test updating push notification preferences - delete(data, model.DESKTOP_NOTIFY_PROP) - delete(data, model.MARK_UNREAD_NOTIFY_PROP) - data[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION - if result, err := Client.UpdateNotifyProps(data); err != nil { - t.Fatal(err) - } else if notifyProps := result.Data.(map[string]string); notifyProps[model.PUSH_NOTIFY_PROP] != model.CHANNEL_NOTIFY_MENTION { - t.Fatal("NotifyProps[\"push\"] did not update properly") - } - - // test updating email preferences - delete(data, model.PUSH_NOTIFY_PROP) - data[model.EMAIL_NOTIFY_PROP] = "true" - if result, err := Client.UpdateNotifyProps(data); err != nil { - t.Fatal(err) - } else if notifyProps := result.Data.(map[string]string); notifyProps[model.EMAIL_NOTIFY_PROP] != "true" { - t.Fatal("NotifyProps[\"email\"] did not update properly") - } - - // test error cases - data["user_id"] = "junk" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad user id") - } - - data["user_id"] = "12345678901234567890123456" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad user id") - } - - data["user_id"] = user.Id - data["channel_id"] = "junk" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad channel id") - } - - data["channel_id"] = "12345678901234567890123456" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad channel id") - } - - data[model.DESKTOP_NOTIFY_PROP] = "junk" - data[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_ALL - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad desktop notify level") - } - - data[model.DESKTOP_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL - data[model.MARK_UNREAD_NOTIFY_PROP] = "junk" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad mark unread level") - } - - data[model.DESKTOP_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL - data[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_ALL - data[model.PUSH_NOTIFY_PROP] = "junk" - data[model.EMAIL_NOTIFY_PROP] = "true" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad push level") - } - - data[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL - data[model.EMAIL_NOTIFY_PROP] = "junk" - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - bad email notification option") - } - - th.LoginBasic2() - - data["channel_id"] = channel1.Id - data["user_id"] = user2.Id - data[model.DESKTOP_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION - data[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION - if _, err := Client.UpdateNotifyProps(data); err == nil { - t.Fatal("Should have errored - user not in channel") - } -} - -func TestFuzzyChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - // Strings that should pass as acceptable channel names - var fuzzyStringsPass = []string{ - "*", "?", ".", "}{][)(><", "{}[]()<>", - - "qahwah ( قهوة)", - "שָׁלוֹם עֲלֵיכֶם", - "Ramen チャーシュー chāshū", - "言而无信", - "Ṫ͌ó̍ ̍͂̓̍̍̀i̊ͯ͒", - "& < &qu", - - "' or '1'='1' -- ", - "' or '1'='1' ({ ", - "' or '1'='1' /* ", - "1;DROP TABLE users", - - "", - - "sue@thatmightbe", - "sue@thatmightbe.", - "sue@thatmightbe.c", - "sue@thatmightbe.co", - "su+san@thatmightbe.com", - "a@b.中国", - "1@2.am", - "a@b.co.uk", - "a@b.cancerresearch", - "local@[127.0.0.1]", - } - - for i := 0; i < len(fuzzyStringsPass); i++ { - channel := model.Channel{DisplayName: fuzzyStringsPass[i], Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - - _, err := Client.CreateChannel(&channel) - if err != nil { - t.Fatal(err) - } - } -} - -func TestGetChannelMember(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - if result, err := Client.GetChannelMember(channel1.Id, th.BasicUser.Id); err != nil { - t.Fatal(err) - } else { - cm := result.Data.(*model.ChannelMember) - - if cm.UserId != th.BasicUser.Id { - t.Fatal("user ids didn't match") - } - if cm.ChannelId != channel1.Id { - t.Fatal("channel ids didn't match") - } - } - - if _, err := Client.GetChannelMember(channel1.Id, th.BasicUser2.Id); err == nil { - t.Fatal("should have failed - user not in channel") - } - - if _, err := Client.GetChannelMember("junk", th.BasicUser2.Id); err == nil { - t.Fatal("should have failed - bad channel id") - } - - if _, err := Client.GetChannelMember(channel1.Id, "junk"); err == nil { - t.Fatal("should have failed - bad user id") - } - - if _, err := Client.GetChannelMember("junk", "junk"); err == nil { - t.Fatal("should have failed - bad channel and user id") - } -} - -func TestSearchMoreChannels(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "TestAPINameA", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "TestAPINameB", Name: "b" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - th.LoginBasic2() - - if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "TestAPIName"}); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - - if (*channels)[1].DisplayName != channel2.DisplayName { - t.Fatal("full name didn't match") - } - } - - if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "TestAPINameA"}); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - } - - if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "TestAPINameB"}); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel2.DisplayName { - t.Fatal("full name didn't match") - } - } - - if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: channel1.Name}); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - } - - if _, err := Client.SearchMoreChannels(model.ChannelSearch{Term: ""}); err == nil { - t.Fatal("should have errored - empty term") - } - - if result, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "blargh"}); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if len(*channels) != 0 { - t.Fatal("should have no channels") - } - } - - Client.SetTeamId("junk") - if _, err := Client.SearchMoreChannels(model.ChannelSearch{Term: "blargh"}); err == nil { - t.Fatal("should have errored - bad team id") - } -} - -func TestAutocompleteChannels(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - - channel1 := &model.Channel{DisplayName: "TestAPINameA", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - channel2 := &model.Channel{DisplayName: "TestAPINameB", Name: "b" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - channel3 := &model.Channel{DisplayName: "BadChannelC", Name: "c" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: model.NewId()} - if _, err := th.SystemAdminClient.CreateChannel(channel3); err == nil { - t.Fatal("channel must have valid team id") - } - - channel4 := &model.Channel{DisplayName: "BadChannelD", Name: "d" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel4 = Client.Must(Client.CreateChannel(channel4)).Data.(*model.Channel) - - if result, err := Client.AutocompleteChannels("TestAPIName"); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - - if (*channels)[1].DisplayName != channel2.DisplayName { - t.Fatal("full name didn't match") - } - } - - if result, err := Client.AutocompleteChannels(channel1.Name); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if (*channels)[0].DisplayName != channel1.DisplayName { - t.Fatal("full name didn't match") - } - } - - if result, err := Client.AutocompleteChannels("BadChannelC"); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if len(*channels) != 0 { - t.Fatal("should have been empty") - } - } - - if result, err := Client.AutocompleteChannels("BadChannelD"); err != nil { - t.Fatal(err) - } else { - channels := result.Data.(*model.ChannelList) - - if len(*channels) != 0 { - t.Fatal("should have been empty") - } - } - - Client.SetTeamId("junk") - - if _, err := Client.AutocompleteChannels("BadChannelD"); err == nil { - t.Fatal("should have failed - bad team id") - } -} - -func TestGetChannelByName(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if result, err := Client.GetChannelByName(th.BasicChannel.Name); err != nil { - t.Fatal("Failed to get channel") - } else { - channel := result.Data.(*model.Channel) - if channel.Name != th.BasicChannel.Name { - t.Fatal("channel names did not match") - } - } - - if _, err := Client.GetChannelByName("InvalidChannelName"); err == nil { - t.Fatal("Failed to get team") - } - - Client.Must(Client.Logout()) - - user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Jabba the Hutt", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - Client.SetTeamId(th.BasicTeam.Id) - - Client.Login(user2.Email, "passwd1") - - if _, err := Client.GetChannelByName(th.BasicChannel.Name); err == nil { - t.Fatal("Should fail due to not enough permissions") - } -} - -func TestViewChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - view := model.ChannelView{ - ChannelId: th.BasicChannel.Id, - } - - if _, resp := Client.ViewChannel(view); resp.Error != nil { - t.Fatal(resp.Error) - } - - view.PrevChannelId = th.BasicChannel.Id - - if _, resp := Client.ViewChannel(view); resp.Error != nil { - t.Fatal(resp.Error) - } - - view.PrevChannelId = "" - - if _, resp := Client.ViewChannel(view); resp.Error != nil { - t.Fatal(resp.Error) - } - - view.PrevChannelId = "junk" - - if _, resp := Client.ViewChannel(view); resp.Error != nil { - t.Fatal(resp.Error) - } - - rdata := Client.Must(Client.GetChannel(th.BasicChannel.Id, "")).Data.(*model.ChannelData) - - if rdata.Channel.TotalMsgCount != rdata.Member.MsgCount { - t.Log(rdata.Channel.Id) - t.Log(rdata.Member.UserId) - t.Log(rdata.Channel.TotalMsgCount) - t.Log(rdata.Member.MsgCount) - t.Fatal("message counts don't match") - } - - if _, err := Client.DoApiPost(Client.GetTeamRoute()+"/channels/view", "garbage"); err == nil { - t.Fatal("should have been an error") - } -} - -func TestGetChannelMembersByIds(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if _, err := th.App.AddUserToChannel(th.BasicUser2, th.BasicChannel); err != nil { - t.Fatal("Could not add second user to channel") - } - - if result, err := th.BasicClient.GetChannelMembersByIds(th.BasicChannel.Id, []string{th.BasicUser.Id}); err != nil { - t.Fatal(err) - } else { - member := (*result.Data.(*model.ChannelMembers))[0] - if member.UserId != th.BasicUser.Id { - t.Fatal("user id did not match") - } - if member.ChannelId != th.BasicChannel.Id { - t.Fatal("team id did not match") - } - } - - if result, err := th.BasicClient.GetChannelMembersByIds(th.BasicChannel.Id, []string{th.BasicUser.Id, th.BasicUser2.Id, model.NewId()}); err != nil { - t.Fatal(err) - } else { - members := result.Data.(*model.ChannelMembers) - if len(*members) != 2 { - t.Fatal("length should have been 2, got ", len(*members)) - } - } - - if _, err := th.BasicClient.GetChannelMembersByIds("junk", []string{th.BasicUser.Id}); err == nil { - t.Fatal("should have errored - bad team id") - } - - if _, err := th.BasicClient.GetChannelMembersByIds(th.BasicChannel.Id, []string{}); err == nil { - t.Fatal("should have errored - empty user ids") - } -} - -func TestUpdateChannelRoles(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - th.SystemAdminClient.SetTeamId(th.BasicTeam.Id) - th.LinkUserToTeam(th.SystemAdminUser, th.BasicTeam) - - const CHANNEL_ADMIN = "channel_admin channel_user" - const CHANNEL_MEMBER = "channel_user" - - // User 1 creates a channel, making them channel admin by default. - createChannel := model.Channel{ - DisplayName: "Test API Name", - Name: "zz" + model.NewId() + "a", - Type: model.CHANNEL_OPEN, - TeamId: th.BasicTeam.Id, - } - - rchannel, err := th.BasicClient.CreateChannel(&createChannel) - if err != nil { - t.Fatal("Failed to create channel:", err) - } - channel := rchannel.Data.(*model.Channel) - - // User 1 adds User 2 to the channel, making them a channel member by default. - if _, err := th.BasicClient.AddChannelMember(channel.Id, th.BasicUser2.Id); err != nil { - t.Fatal("Failed to add user 2 to the channel:", err) - } - - // System Admin can demote User 1 (channel admin). - if data, meta := th.SystemAdminClient.UpdateChannelRoles(channel.Id, th.BasicUser.Id, CHANNEL_MEMBER); data == nil { - t.Fatal("System Admin failed to demote channel admin to channel member:", meta) - } - - // User 1 (channel_member) cannot promote user 2 (channel_member). - if data, meta := th.BasicClient.UpdateChannelRoles(channel.Id, th.BasicUser2.Id, CHANNEL_ADMIN); data != nil { - t.Fatal("Channel member should not be able to promote another channel member to channel admin:", meta) - } - - // System Admin can promote user 1 (channel member). - if data, meta := th.SystemAdminClient.UpdateChannelRoles(channel.Id, th.BasicUser.Id, CHANNEL_ADMIN); data == nil { - t.Fatal("System Admin failed to promote channel member to channel admin:", meta) - } - - // User 1 (channel_admin) can promote User 2 (channel member). - if data, meta := th.BasicClient.UpdateChannelRoles(channel.Id, th.BasicUser2.Id, CHANNEL_ADMIN); data == nil { - t.Fatal("Channel admin failed to promote channel member to channel admin:", meta) - } - - // User 1 (channel admin) can demote User 2 (channel admin). - if data, meta := th.BasicClient.UpdateChannelRoles(channel.Id, th.BasicUser2.Id, CHANNEL_MEMBER); data == nil { - t.Fatal("Channel admin failed to demote channel admin to channel member:", meta) - } - - // User 1 (channel admin) can demote itself. - if data, meta := th.BasicClient.UpdateChannelRoles(channel.Id, th.BasicUser.Id, CHANNEL_MEMBER); data == nil { - t.Fatal("Channel admin failed to demote itself to channel member:", meta) - } - - // Promote User2 again for next test. - if data, meta := th.SystemAdminClient.UpdateChannelRoles(channel.Id, th.BasicUser2.Id, CHANNEL_ADMIN); data == nil { - t.Fatal("System Admin failed to promote channel member to channel admin:", meta) - } - - // User 1 (channel member) cannot demote user 2 (channel admin). - if data, meta := th.BasicClient.UpdateChannelRoles(channel.Id, th.BasicUser2.Id, CHANNEL_MEMBER); data != nil { - t.Fatal("Channel member should not be able to demote another channel admin to channel member:", meta) - } - - // User 1 (channel member) cannot promote itself. - if data, meta := th.BasicClient.UpdateChannelRoles(channel.Id, th.BasicUser.Id, CHANNEL_ADMIN); data != nil { - t.Fatal("Channel member should not be able to promote itself to channel admin:", meta) - } -} - -func TestGetPinnedPosts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - post1 := th.BasicPost - r1 := Client.Must(Client.GetPinnedPosts(post1.ChannelId)).Data.(*model.PostList) - if len(r1.Order) != 0 { - t.Fatal("should not have gotten a pinned post") - } - - post2 := th.PinnedPost - r2 := Client.Must(Client.GetPinnedPosts(post2.ChannelId)).Data.(*model.PostList) - if len(r2.Order) == 0 { - t.Fatal("should have gotten a pinned post") - } - - if _, ok := r2.Posts[post2.Id]; !ok { - t.Fatal("missing pinned post") - } -} diff --git a/api/command.go b/api/command.go deleted file mode 100644 index bbc7fbebd..000000000 --- a/api/command.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "io/ioutil" - "net/http" - "strings" - - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitCommand() { - api.BaseRoutes.Commands.Handle("/execute", api.ApiUserRequired(executeCommand)).Methods("POST") - api.BaseRoutes.Commands.Handle("/list", api.ApiUserRequired(listCommands)).Methods("GET") - - api.BaseRoutes.Commands.Handle("/create", api.ApiUserRequired(createCommand)).Methods("POST") - api.BaseRoutes.Commands.Handle("/update", api.ApiUserRequired(updateCommand)).Methods("POST") - api.BaseRoutes.Commands.Handle("/list_team_commands", api.ApiUserRequired(listTeamCommands)).Methods("GET") - api.BaseRoutes.Commands.Handle("/regen_token", api.ApiUserRequired(regenCommandToken)).Methods("POST") - api.BaseRoutes.Commands.Handle("/delete", api.ApiUserRequired(deleteCommand)).Methods("POST") - - api.BaseRoutes.Teams.Handle("/command_test", api.ApiAppHandler(testCommand)).Methods("POST") - api.BaseRoutes.Teams.Handle("/command_test", api.ApiAppHandler(testCommand)).Methods("GET") - api.BaseRoutes.Teams.Handle("/command_test_e", api.ApiAppHandler(testEphemeralCommand)).Methods("POST") - api.BaseRoutes.Teams.Handle("/command_test_e", api.ApiAppHandler(testEphemeralCommand)).Methods("GET") -} - -func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { - commands, err := c.App.ListAutocompleteCommands(c.TeamId, c.T) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.CommandListToJson(commands))) -} - -func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { - commandArgs := model.CommandArgsFromJson(r.Body) - if commandArgs == nil { - c.SetInvalidParam("executeCommand", "command_args") - return - } - - if len(commandArgs.Command) <= 1 || strings.Index(commandArgs.Command, "/") != 0 { - c.Err = model.NewAppError("executeCommand", "api.command.execute_command.start.app_error", nil, "", http.StatusBadRequest) - return - } - - if len(commandArgs.ChannelId) > 0 { - if !c.App.SessionHasPermissionToChannel(c.Session, commandArgs.ChannelId, model.PERMISSION_USE_SLASH_COMMANDS) { - c.SetPermissionError(model.PERMISSION_USE_SLASH_COMMANDS) - return - } - } - - commandArgs.TeamId = c.TeamId - commandArgs.UserId = c.Session.UserId - commandArgs.T = c.T - commandArgs.Session = c.Session - commandArgs.SiteURL = c.GetSiteURLHeader() - - response, err := c.App.ExecuteCommand(commandArgs) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(response.ToJson())) -} - -func createCommand(c *Context, w http.ResponseWriter, r *http.Request) { - cmd := model.CommandFromJson(r.Body) - - if cmd == nil { - c.SetInvalidParam("createCommand", "command") - return - } - - c.LogAudit("attempt") - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) - return - } - - cmd.CreatorId = c.Session.UserId - cmd.TeamId = c.TeamId - - rcmd, err := c.App.CreateCommand(cmd) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - w.Write([]byte(rcmd.ToJson())) -} - -func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) { - cmd := model.CommandFromJson(r.Body) - - if cmd == nil { - c.SetInvalidParam("updateCommand", "command") - return - } - - c.LogAudit("attempt") - - oldCmd, err := c.App.GetCommand(cmd.Id) - if err != nil { - c.Err = err - return - } - - if c.TeamId != oldCmd.TeamId { - c.Err = model.NewAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) - return - } - - if !c.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 && !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) - return - } - - rcmd, err := c.App.UpdateCommand(oldCmd, cmd) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - - w.Write([]byte(rcmd.ToJson())) -} - -func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) - return - } - - cmds, err := c.App.ListTeamCommands(c.TeamId) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.CommandListToJson(cmds))) -} - -func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("regenCommandToken", "id") - return - } - - c.LogAudit("attempt") - - cmd, err := c.App.GetCommand(id) - if err != nil { - c.Err = err - return - } - - if c.TeamId != cmd.TeamId { - c.Err = model.NewAppError("regenCommandToken", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) - return - } - - if !c.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 && !c.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 := c.App.RegenCommandToken(cmd) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(rcmd.ToJson())) -} - -func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteCommand", "id") - return - } - - c.LogAudit("attempt") - - cmd, err := c.App.GetCommand(id) - if err != nil { - c.Err = err - return - } - - if c.TeamId != cmd.TeamId { - c.Err = model.NewAppError("deleteCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) - c.LogAudit("fail - inappropriate permissions") - return - } - - if c.Session.UserId != cmd.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) - c.LogAudit("fail - inappropriate permissions") - return - } - - err = c.App.DeleteCommand(cmd.Id) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - w.Write([]byte(model.MapToJson(props))) -} - -func testCommand(c *Context, w http.ResponseWriter, r *http.Request) { - r.ParseForm() - - msg := "" - if r.Method == "POST" { - msg = msg + "\ntoken=" + r.FormValue("token") - msg = msg + "\nteam_domain=" + r.FormValue("team_domain") - } else { - body, _ := ioutil.ReadAll(r.Body) - msg = string(body) - } - - rc := &model.CommandResponse{ - Text: "test command response " + msg, - ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, - } - - w.Write([]byte(rc.ToJson())) -} - -func testEphemeralCommand(c *Context, w http.ResponseWriter, r *http.Request) { - r.ParseForm() - - msg := "" - if r.Method == "POST" { - msg = msg + "\ntoken=" + r.FormValue("token") - msg = msg + "\nteam_domain=" + r.FormValue("team_domain") - } else { - body, _ := ioutil.ReadAll(r.Body) - msg = string(body) - } - - rc := &model.CommandResponse{ - Text: "test command response " + msg, - ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, - } - - w.Write([]byte(rc.ToJson())) -} diff --git a/api/command_echo_test.go b/api/command_echo_test.go deleted file mode 100644 index 7a8697c19..000000000 --- a/api/command_echo_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestEchoCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - echoTestString := "/echo test" - - if r1 := Client.Must(Client.Command(channel1.Id, echoTestString)).Data.(*model.CommandResponse); r1 == nil { - t.Fatal("Echo command failed to execute") - } - - if r1 := Client.Must(Client.Command(channel1.Id, "/echo ")).Data.(*model.CommandResponse); r1 == nil { - t.Fatal("Echo command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) - if len(p1.Order) != 2 { - t.Fatal("Echo command failed to send") - } -} diff --git a/api/command_expand_collapse_test.go b/api/command_expand_collapse_test.go deleted file mode 100644 index 96f92c58f..000000000 --- a/api/command_expand_collapse_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestExpandCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - r1 := Client.Must(Client.Command(channel.Id, "/expand")).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPreference(model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_COLLAPSE_SETTING)).Data.(*model.Preference) - if p1.Value != "false" { - t.Fatal("preference not updated correctly") - } -} - -func TestCollapseCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - r1 := Client.Must(Client.Command(channel.Id, "/collapse")).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPreference(model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_COLLAPSE_SETTING)).Data.(*model.Preference) - if p1.Value != "true" { - t.Fatal("preference not updated correctly") - } -} diff --git a/api/command_groupmsg_test.go b/api/command_groupmsg_test.go deleted file mode 100644 index fcb9953ad..000000000 --- a/api/command_groupmsg_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestGroupmsgCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user1 := th.BasicUser - user2 := th.BasicUser2 - user3 := th.CreateUser(th.BasicClient) - user4 := th.CreateUser(th.BasicClient) - user5 := th.CreateUser(th.BasicClient) - user6 := th.CreateUser(th.BasicClient) - user7 := th.CreateUser(th.BasicClient) - user8 := th.CreateUser(th.BasicClient) - user9 := th.CreateUser(th.BasicClient) - th.LinkUserToTeam(user3, team) - th.LinkUserToTeam(user4, team) - - rs1 := Client.Must(Client.Command("", "/groupmsg "+user2.Username+","+user3.Username)).Data.(*model.CommandResponse) - - group1 := model.GetGroupNameFromUserIds([]string{user1.Id, user2.Id, user3.Id}) - - if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+group1) { - t.Fatal("failed to create group channel") - } - - rs2 := Client.Must(Client.Command("", "/groupmsg "+user3.Username+","+user4.Username+" foobar")).Data.(*model.CommandResponse) - group2 := model.GetGroupNameFromUserIds([]string{user1.Id, user3.Id, user4.Id}) - - if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+group2) { - t.Fatal("failed to create second direct channel") - } - if result := Client.Must(Client.SearchPosts("foobar", false)).Data.(*model.PostList); len(result.Order) == 0 { - t.Fatal("post did not get sent to direct message") - } - - rs3 := Client.Must(Client.Command("", "/groupmsg "+user2.Username+","+user3.Username)).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+group1) { - t.Fatal("failed to go back to existing group channel") - } - - Client.Must(Client.Command("", "/groupmsg "+user2.Username+" foobar")) - Client.Must(Client.Command("", "/groupmsg "+user2.Username+","+user3.Username+","+user4.Username+","+user5.Username+","+user6.Username+","+user7.Username+","+user8.Username+","+user9.Username+" foobar")) - Client.Must(Client.Command("", "/groupmsg junk foobar")) - Client.Must(Client.Command("", "/groupmsg junk,junk2 foobar")) -} diff --git a/api/command_help_test.go b/api/command_help_test.go deleted file mode 100644 index 181298d7e..000000000 --- a/api/command_help_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestHelpCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - HelpLink := *th.App.Config().SupportSettings.HelpLink - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.SupportSettings.HelpLink = HelpLink }) - }() - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.SupportSettings.HelpLink = "" }) - rs1 := Client.Must(Client.Command(channel.Id, "/help ")).Data.(*model.CommandResponse) - if rs1.GotoLocation != model.SUPPORT_SETTINGS_DEFAULT_HELP_LINK { - t.Fatal("failed to default help link") - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.SupportSettings.HelpLink = "https://docs.mattermost.com/guides/user.html" - }) - rs2 := Client.Must(Client.Command(channel.Id, "/help ")).Data.(*model.CommandResponse) - if rs2.GotoLocation != "https://docs.mattermost.com/guides/user.html" { - t.Fatal("failed to help link") - } -} diff --git a/api/command_invite_people_test.go b/api/command_invite_people_test.go deleted file mode 100644 index 950e2c2a8..000000000 --- a/api/command_invite_people_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestInvitePeopleCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - r1 := Client.Must(Client.Command(channel.Id, "/invite_people test@example.com")).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Command failed to execute") - } - - r2 := Client.Must(Client.Command(channel.Id, "/invite_people test1@example.com test2@example.com")).Data.(*model.CommandResponse) - if r2 == nil { - t.Fatal("Command failed to execute") - } - - r3 := Client.Must(Client.Command(channel.Id, "/invite_people")).Data.(*model.CommandResponse) - if r3 == nil { - t.Fatal("Command failed to execute") - } -} diff --git a/api/command_join_test.go b/api/command_join_test.go deleted file mode 100644 index 7926f9bf6..000000000 --- a/api/command_join_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -// also used to test /open (see command_open_test.go) -func testJoinCommands(t *testing.T, alias string) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user2 := th.BasicUser2 - - channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel0 = Client.Must(Client.CreateChannel(channel0)).Data.(*model.Channel) - - channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - Client.Must(Client.LeaveChannel(channel1.Id)) - - channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.Must(Client.LeaveChannel(channel2.Id)) - - channel3 := Client.Must(Client.CreateDirectChannel(user2.Id)).Data.(*model.Channel) - - rs5 := Client.Must(Client.Command(channel0.Id, "/"+alias+" "+channel2.Name)).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { - t.Fatal("failed to join channel") - } - - rs6 := Client.Must(Client.Command(channel0.Id, "/"+alias+" "+channel3.Name)).Data.(*model.CommandResponse) - if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { - t.Fatal("should not have joined direct message channel") - } - - c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - - found := false - for _, c := range *c1 { - if c.Id == channel2.Id { - found = true - } - } - - if !found { - t.Fatal("did not join channel") - } -} - -func TestJoinCommands(t *testing.T) { - testJoinCommands(t, "join") -} diff --git a/api/command_leave_test.go b/api/command_leave_test.go deleted file mode 100644 index 17855c851..000000000 --- a/api/command_leave_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestLeaveCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user2 := th.BasicUser2 - - channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - Client.Must(Client.JoinChannel(channel1.Id)) - - channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.Must(Client.JoinChannel(channel2.Id)) - Client.Must(Client.AddChannelMember(channel2.Id, user2.Id)) - - channel3 := Client.Must(Client.CreateDirectChannel(user2.Id)).Data.(*model.Channel) - - rs1 := Client.Must(Client.Command(channel1.Id, "/leave")).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { - t.Fatal("failed to leave open channel 1") - } - - rs2 := Client.Must(Client.Command(channel2.Id, "/leave")).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { - t.Fatal("failed to leave private channel 1") - } - - rs3 := Client.Must(Client.Command(channel3.Id, "/leave")).Data.(*model.CommandResponse) - if strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { - t.Fatal("should not have left direct message channel") - } - - cdata := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - - found := false - for _, c := range *cdata { - if c.Id == channel1.Id || c.Id == channel2.Id { - found = true - } - } - - if found { - t.Fatal("did not leave right channels") - } - - for _, c := range *cdata { - if c.Name == model.DEFAULT_CHANNEL { - if _, err := Client.LeaveChannel(c.Id); err == nil { - t.Fatal("should have errored on leaving default channel") - } - break - } - } -} diff --git a/api/command_loadtest_test.go b/api/command_loadtest_test.go deleted file mode 100644 index 570b8a09b..000000000 --- a/api/command_loadtest_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestLoadTestHelpCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) - - rs := Client.Must(Client.Command(channel.Id, "/test help")).Data.(*model.CommandResponse) - if !strings.Contains(rs.Text, "Mattermost testing commands to help") { - t.Fatal(rs.Text) - } - - time.Sleep(2 * time.Second) -} - -func TestLoadTestSetupCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) - - rs := Client.Must(Client.Command(channel.Id, "/test setup fuzz 1 1 1")).Data.(*model.CommandResponse) - if rs.Text != "Created environment" { - t.Fatal(rs.Text) - } - - time.Sleep(2 * time.Second) -} - -func TestLoadTestUsersCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) - - rs := Client.Must(Client.Command(channel.Id, "/test users fuzz 1 2")).Data.(*model.CommandResponse) - if rs.Text != "Added users" { - t.Fatal(rs.Text) - } - - time.Sleep(2 * time.Second) -} - -func TestLoadTestChannelsCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) - - rs := Client.Must(Client.Command(channel.Id, "/test channels fuzz 1 2")).Data.(*model.CommandResponse) - if rs.Text != "Added channels" { - t.Fatal(rs.Text) - } - - time.Sleep(2 * time.Second) -} - -func TestLoadTestPostsCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) - - rs := Client.Must(Client.Command(channel.Id, "/test posts fuzz 2 3 2")).Data.(*model.CommandResponse) - if rs.Text != "Added posts" { - t.Fatal(rs.Text) - } - - time.Sleep(2 * time.Second) -} diff --git a/api/command_logout_test.go b/api/command_logout_test.go deleted file mode 100644 index cbd75dd93..000000000 --- a/api/command_logout_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestLogoutTestCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/logout")) -} diff --git a/api/command_me_test.go b/api/command_me_test.go deleted file mode 100644 index 63718ca68..000000000 --- a/api/command_me_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestMeCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - testString := "/me hello" - - r1 := Client.Must(Client.Command(channel.Id, testString)).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPosts(channel.Id, 0, 2, "")).Data.(*model.PostList) - if len(p1.Order) != 2 { - t.Fatal("Command failed to send") - } else { - if p1.Posts[p1.Order[0]].Message != `*hello*` { - t.Log(p1.Posts[p1.Order[0]].Message) - t.Fatal("invalid shrug response") - } - } -} diff --git a/api/command_msg_test.go b/api/command_msg_test.go deleted file mode 100644 index 07cce149c..000000000 --- a/api/command_msg_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestMsgCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - user1 := th.BasicUser - user2 := th.BasicUser2 - user3 := th.CreateUser(th.BasicClient) - th.LinkUserToTeam(user3, team) - - Client.Must(Client.CreateDirectChannel(user2.Id)) - Client.Must(Client.CreateDirectChannel(user3.Id)) - - rs1 := Client.Must(Client.Command("", "/msg "+user2.Username)).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { - t.Fatal("failed to create direct channel") - } - - rs2 := Client.Must(Client.Command("", "/msg "+user3.Username+" foobar")).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) { - t.Fatal("failed to create second direct channel") - } - if result := Client.Must(Client.SearchPosts("foobar", false)).Data.(*model.PostList); len(result.Order) == 0 { - t.Fatalf("post did not get sent to direct message") - } - - rs3 := Client.Must(Client.Command("", "/msg "+user2.Username)).Data.(*model.CommandResponse) - if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { - t.Fatal("failed to go back to existing direct channel") - } - - Client.Must(Client.Command("", "/msg "+th.BasicUser.Username+" foobar")) - Client.Must(Client.Command("", "/msg junk foobar")) -} diff --git a/api/command_open_test.go b/api/command_open_test.go deleted file mode 100644 index 61e3861d8..000000000 --- a/api/command_open_test.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestOpenCommands(t *testing.T) { - testJoinCommands(t, "open") -} diff --git a/api/command_search_test.go b/api/command_search_test.go deleted file mode 100644 index a7f187d36..000000000 --- a/api/command_search_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestSearchCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/search")) -} diff --git a/api/command_settings_test.go b/api/command_settings_test.go deleted file mode 100644 index af137ce97..000000000 --- a/api/command_settings_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestSettingsCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/settings")) -} diff --git a/api/command_shortcuts_test.go b/api/command_shortcuts_test.go deleted file mode 100644 index e6ff17910..000000000 --- a/api/command_shortcuts_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestShortcutsCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/shortcuts")) -} diff --git a/api/command_shrug_test.go b/api/command_shrug_test.go deleted file mode 100644 index 9b453a48d..000000000 --- a/api/command_shrug_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestShrugCommand(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel := th.BasicChannel - - testString := "/shrug" - - r1 := Client.Must(Client.Command(channel.Id, testString)).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPosts(channel.Id, 0, 2, "")).Data.(*model.PostList) - if len(p1.Order) != 2 { - t.Fatal("Command failed to send") - } else { - if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` { - t.Log(p1.Posts[p1.Order[0]].Message) - t.Fatal("invalid shrug response") - } - } -} diff --git a/api/command_statuses_test.go b/api/command_statuses_test.go deleted file mode 100644 index 0ac651636..000000000 --- a/api/command_statuses_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestStatusCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - commandAndTest(t, th, "away") - commandAndTest(t, th, "offline") - commandAndTest(t, th, "online") -} - -func commandAndTest(t *testing.T, th *TestHelper, status string) { - Client := th.BasicClient - channel := th.BasicChannel - user := th.BasicUser - - r1 := Client.Must(Client.Command(channel.Id, "/"+status)).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Command failed to execute") - } - - time.Sleep(1000 * time.Millisecond) - - statuses := Client.Must(Client.GetStatuses()).Data.(map[string]string) - - if status == "offline" { - status = "" - } - if statuses[user.Id] != status { - t.Fatal("Error setting status " + status) - } -} diff --git a/api/command_test.go b/api/command_test.go deleted file mode 100644 index 7f9c6dcff..000000000 --- a/api/command_test.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" -) - -func TestListCommands(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if results, err := Client.ListCommands(); err != nil { - t.Fatal(err) - } else { - commands := results.Data.([]*model.Command) - foundEcho := false - - for _, command := range commands { - if command.Trigger == "echo" { - foundEcho = true - } - } - - if !foundEcho { - t.Fatal("Couldn't find echo command") - } - } -} - -func TestCreateCommand(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - user := th.SystemAdminUser - team := th.SystemAdminTeam - - enableCommands := *th.App.Config().ServiceSettings.EnableCommands - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - - cmd1 := &model.Command{ - CreatorId: user.Id, - TeamId: team.Id, - URL: "http://nowhere.com", - Method: model.COMMAND_METHOD_POST, - Trigger: "trigger"} - - if _, err := Client.CreateCommand(cmd1); err == nil { - t.Fatal("should have failed because not admin") - } - - Client = th.SystemAdminClient - - cmd2 := &model.Command{ - CreatorId: user.Id, - TeamId: team.Id, - URL: "http://nowhere.com", - Method: model.COMMAND_METHOD_POST, - Trigger: "trigger"} - - var rcmd *model.Command - if result, err := Client.CreateCommand(cmd2); err != nil { - t.Fatal(err) - } else { - rcmd = result.Data.(*model.Command) - } - - if rcmd.CreatorId != user.Id { - t.Fatal("user ids didn't match") - } - - if rcmd.TeamId != team.Id { - t.Fatal("team ids didn't match") - } - - cmd3 := &model.Command{ - CreatorId: "123", - TeamId: "456", - URL: "http://nowhere.com", - Method: model.COMMAND_METHOD_POST, - Trigger: "trigger"} - if _, err := Client.CreateCommand(cmd3); err == nil { - t.Fatal("trigger cannot be duplicated") - } - - cmd4 := cmd3 - if _, err := Client.CreateCommand(cmd4); err == nil { - t.Fatal("command cannot be duplicated") - } -} - -func TestListTeamCommands(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - - enableCommands := *th.App.Config().ServiceSettings.EnableCommands - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - - cmd1 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"} - cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command) - - if result, err := Client.ListTeamCommands(); err != nil { - t.Fatal(err) - } else { - cmds := result.Data.([]*model.Command) - - if len(cmds) != 1 { - t.Fatal("incorrect number of cmd") - } - } -} - -func TestUpdateCommand(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - user := th.SystemAdminUser - team := th.SystemAdminTeam - - enableCommands := *th.App.Config().ServiceSettings.EnableCommands - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - - cmd1 := &model.Command{ - CreatorId: user.Id, - TeamId: team.Id, - URL: "http://nowhere.com", - Method: model.COMMAND_METHOD_POST, - Trigger: "trigger"} - - cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command) - - cmd2 := &model.Command{ - CreatorId: user.Id, - TeamId: team.Id, - URL: "http://nowhere.com", - Method: model.COMMAND_METHOD_POST, - Trigger: "trigger2", - Token: cmd1.Token, - Id: cmd1.Id} - - if result, err := Client.UpdateCommand(cmd2); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.Command).Trigger == cmd1.Trigger { - t.Fatal("update didn't work properly") - } - } -} - -func TestRegenToken(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - - enableCommands := *th.App.Config().ServiceSettings.EnableCommands - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - - cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"} - cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) - - data := make(map[string]string) - data["id"] = cmd.Id - - if result, err := Client.RegenCommandToken(data); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.Command).Token == cmd.Token { - t.Fatal("regen didn't work properly") - } - } -} - -func TestDeleteCommand(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - - enableCommands := *th.App.Config().ServiceSettings.EnableCommands - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = enableCommands }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - - cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"} - cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) - - data := make(map[string]string) - data["id"] = cmd.Id - - if _, err := Client.DeleteCommand(data); err != nil { - t.Fatal(err) - } - - cmds := Client.Must(Client.ListTeamCommands()).Data.([]*model.Command) - if len(cmds) != 0 { - t.Fatal("delete didn't work properly") - } - - cmd2 := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger2"} - cmd2 = Client.Must(Client.CreateCommand(cmd2)).Data.(*model.Command) - - data2 := make(map[string]string) - data2["id"] = cmd2.Id - if _, err := th.BasicClient.DeleteCommand(data2); err == nil { - t.Fatal("Should have errored. Your not allowed to delete other's commands") - } - - cmds2 := Client.Must(Client.ListTeamCommands()).Data.([]*model.Command) - if len(cmds2) != 1 { - t.Fatal("Client was able to delete command without permission.") - } -} - -func TestTestCommand(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - channel1 := th.SystemAdminChannel - - enableCommands := *th.App.Config().ServiceSettings.EnableCommands - allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands }) - th.App.UpdateConfig(func(cfg *model.Config) { - cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections - }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost" }) - - cmd1 := &model.Command{ - URL: fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port) + model.API_URL_SUFFIX_V3 + "/teams/command_test", - Method: model.COMMAND_METHOD_POST, - Trigger: "testcommand", - } - - cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command) - - r1 := Client.Must(Client.Command(channel1.Id, "/testcommand")).Data.(*model.CommandResponse) - if r1 == nil { - t.Fatal("Test command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList) - // including system 'joined' message - if len(p1.Order) != 2 { - t.Fatal("Test command response failed to send") - } - - cmdPosted := false - for _, post := range p1.Posts { - if strings.Contains(post.Message, "test command response") { - cmdPosted = true - break - } - } - - if !cmdPosted { - t.Fatal("Test command response failed to post") - } - - cmd2 := &model.Command{ - URL: fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port) + model.API_URL_SUFFIX_V3 + "/teams/command_test", - Method: model.COMMAND_METHOD_GET, - Trigger: "test2", - } - - cmd2 = Client.Must(Client.CreateCommand(cmd2)).Data.(*model.Command) - - r2 := Client.Must(Client.Command(channel1.Id, "/test2")).Data.(*model.CommandResponse) - if r2 == nil { - t.Fatal("Test2 command failed to execute") - } - - time.Sleep(100 * time.Millisecond) - - p2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList) - if len(p2.Order) != 3 { - t.Fatal("Test command failed to send") - } -} diff --git a/api/context.go b/api/context.go deleted file mode 100644 index 8ebb5f73b..000000000 --- a/api/context.go +++ /dev/null @@ -1,451 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "net/http" - "net/url" - "strings" - "sync/atomic" - "time" - - "github.com/gorilla/mux" - goi18n "github.com/nicksnyder/go-i18n/i18n" - - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" -) - -type Context struct { - App *app.App - Session model.Session - RequestId string - IpAddress string - Path string - Err *model.AppError - siteURLHeader string - teamURLValid bool - teamURL string - T goi18n.TranslateFunc - Locale string - TeamId string -} - -func (api *API) ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, false, false, true, false, false, false, false} -} - -func (api *API) AppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, false, false, false, false, false, false, false} -} - -func (api *API) AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, false, false, false, false, true, false, false} -} - -func (api *API) ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, true, false, true, false, false, false, true} -} - -func (api *API) ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler { - return &handler{api.App, h, true, false, true, isUserActivity, false, false, true} -} - -func (api *API) ApiUserRequiredMfa(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, true, false, true, false, false, false, false} -} - -func (api *API) UserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, true, false, false, false, false, false, true} -} - -func (api *API) AppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, false, false, false, false, false, true, false} -} - -func (api *API) ApiAdminSystemRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, true, true, true, false, false, false, true} -} - -func (api *API) ApiAdminSystemRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, true, true, true, false, false, true, true} -} - -func (api *API) ApiAppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, false, false, true, false, false, true, false} -} - -func (api *API) ApiUserRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, true, false, true, false, false, true, true} -} - -func (api *API) ApiAppHandlerTrustRequesterIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{api.App, h, false, false, true, false, true, true, false} -} - -type handler struct { - app *app.App - handleFunc func(*Context, http.ResponseWriter, *http.Request) - requireUser bool - requireSystemAdmin bool - isApi bool - isUserActivity bool - isTeamIndependent bool - trustRequester bool - requireMfa bool -} - -func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - now := time.Now() - mlog.Debug(fmt.Sprintf("%v", r.URL.Path)) - - c := &Context{} - c.App = h.app - c.T, c.Locale = utils.GetTranslationsAndLocale(w, r) - c.RequestId = model.NewId() - c.IpAddress = utils.GetIpAddress(r) - c.TeamId = mux.Vars(r)["team_id"] - - if metrics := c.App.Metrics; metrics != nil && h.isApi { - metrics.IncrementHttpRequest() - } - - token, tokenLocation := app.ParseAuthTokenFromRequest(r) - - // CSRF Check - if tokenLocation == app.TokenLocationCookie && (h.requireSystemAdmin || h.requireUser) && !h.trustRequester { - if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML { - c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) - token = "" - } - } - - c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host) - - w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) - w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil)) - - // Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking - if !h.isApi { - w.Header().Set("X-Frame-Options", "SAMEORIGIN") - w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'") - } else { - // All api response bodies will be JSON formatted by default - w.Header().Set("Content-Type", "application/json") - - if r.Method == "GET" { - w.Header().Set("Expires", "0") - } - } - - if len(token) != 0 { - session, err := c.App.GetSession(token) - - if err != nil { - mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error())) - c.RemoveSessionCookie(w, r) - if h.requireUser || h.requireSystemAdmin { - c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) - } - } else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString { - c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) - } else { - c.Session = *session - } - - // Rate limit by UserID - if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.Session.UserId, w) { - return - } - } - - if h.isApi || h.isTeamIndependent { - c.setTeamURL(c.GetSiteURLHeader(), false) - c.Path = r.URL.Path - } else { - splitURL := strings.Split(r.URL.Path, "/") - c.setTeamURL(c.GetSiteURLHeader()+"/"+splitURL[1], true) - c.Path = "/" + strings.Join(splitURL[2:], "/") - } - - if h.isApi && !*c.App.Config().ServiceSettings.EnableAPIv3 { - c.Err = model.NewAppError("ServeHTTP", "api.context.v3_disabled.app_error", nil, "", http.StatusNotImplemented) - } - - if c.Err == nil && h.requireUser { - c.UserRequired() - } - - if c.Err == nil && h.requireMfa { - c.MfaRequired() - } - - if c.Err == nil && h.requireSystemAdmin { - c.SystemAdminRequired() - } - - if c.Err == nil && h.isUserActivity && token != "" && len(c.Session.UserId) > 0 { - c.App.SetStatusOnline(c.Session.UserId, c.Session.Id, false) - c.App.UpdateLastActivityAtIfNeeded(c.Session) - } - - if c.Err == nil && (h.requireUser || h.requireSystemAdmin) { - //check if teamId exist - c.CheckTeamId() - } - - if h.isApi { - atomic.StoreInt32(model.UsedApiV3, 1) - } - - if c.Err == nil { - h.handleFunc(c, w, r) - } - - // Handle errors that have occoured - if c.Err != nil { - c.Err.Translate(c.T) - c.Err.RequestId = c.RequestId - c.LogError(c.Err) - c.Err.Where = r.URL.Path - - // Block out detailed error when not in developer mode - if !*c.App.Config().ServiceSettings.EnableDeveloper { - c.Err.DetailedError = "" - } - - if h.isApi { - w.WriteHeader(c.Err.StatusCode) - w.Write([]byte(c.Err.ToJson())) - - if c.App.Metrics != nil { - c.App.Metrics.IncrementHttpError() - } - } else { - if c.Err.StatusCode == http.StatusUnauthorized { - http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) - } else { - utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) - } - } - - } - - if h.isApi && c.App.Metrics != nil { - if r.URL.Path != model.API_URL_SUFFIX_V3+"/users/websocket" { - elapsed := float64(time.Since(now)) / float64(time.Second) - c.App.Metrics.ObserveHttpRequestDuration(elapsed) - } - } -} - -func (c *Context) LogAudit(extraInfo string) { - audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id} - if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil { - c.LogError(r.Err) - } -} - -func (c *Context) LogAuditWithUserId(userId, extraInfo string) { - - if len(c.Session.UserId) > 0 { - extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId) - } - - audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id} - if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil { - c.LogError(r.Err) - } -} - -func (c *Context) LogError(err *model.AppError) { - - // filter out endless reconnects - if c.Path == "/api/v3/users/websocket" && err.StatusCode == 401 || err.Id == "web.check_browser_compatibility.app_error" { - c.LogDebug(err) - } else if err.Id != "api.post.create_post.town_square_read_only" { - mlog.Error(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) - } -} - -func (c *Context) LogDebug(err *model.AppError) { - mlog.Debug(fmt.Sprintf("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode, - c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError), mlog.String("user_id", c.Session.UserId)) -} - -func (c *Context) UserRequired() { - if !*c.App.Config().ServiceSettings.EnableUserAccessTokens && c.Session.Props[model.SESSION_PROP_TYPE] == model.SESSION_TYPE_USER_ACCESS_TOKEN { - c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized) - return - } - - if len(c.Session.UserId) == 0 { - c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized) - return - } -} - -func (c *Context) MfaRequired() { - // Must be licensed for MFA and have it configured for enforcement - if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication { - return - } - - // OAuth integrations are excepted - if c.Session.IsOAuth { - return - } - - if result := <-c.App.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { - c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "MfaRequired", http.StatusUnauthorized) - return - } else { - user := result.Data.(*model.User) - - // Only required for email and ldap accounts - if user.AuthService != "" && - user.AuthService != model.USER_AUTH_SERVICE_EMAIL && - user.AuthService != model.USER_AUTH_SERVICE_LDAP { - return - } - - if !user.MfaActive { - c.Err = model.NewAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired", http.StatusUnauthorized) - return - } - } -} - -func (c *Context) SystemAdminRequired() { - if len(c.Session.UserId) == 0 { - c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "SystemAdminRequired", http.StatusUnauthorized) - return - } else if !c.IsSystemAdmin() { - c.Err = model.NewAppError("", "api.context.permissions.app_error", nil, "AdminRequired", http.StatusForbidden) - return - } -} - -func (c *Context) IsSystemAdmin() bool { - return c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) -} - -func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) { - cookie := &http.Cookie{ - Name: model.SESSION_COOKIE_TOKEN, - Value: "", - Path: "/", - MaxAge: -1, - HttpOnly: true, - } - - userCookie := &http.Cookie{ - Name: model.SESSION_COOKIE_USER, - Value: "", - Path: "/", - MaxAge: -1, - } - - http.SetCookie(w, cookie) - http.SetCookie(w, userCookie) -} - -func (c *Context) SetInvalidParam(where string, name string) { - c.Err = NewInvalidParamError(where, name) -} - -func NewInvalidParamError(where string, name string) *model.AppError { - err := model.NewAppError(where, "api.context.invalid_param.app_error", map[string]interface{}{"Name": name}, "", http.StatusBadRequest) - return err -} - -func (c *Context) SetPermissionError(permission *model.Permission) { - c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden) -} - -func (c *Context) setTeamURL(url string, valid bool) { - c.teamURL = url - c.teamURLValid = valid -} - -func (c *Context) SetTeamURLFromSession() { - if result := <-c.App.Srv.Store.Team().Get(c.TeamId); result.Err == nil { - c.setTeamURL(c.GetSiteURLHeader()+"/"+result.Data.(*model.Team).Name, true) - } -} - -func (c *Context) SetSiteURLHeader(url string) { - c.siteURLHeader = strings.TrimRight(url, "/") -} - -func (c *Context) GetTeamURL() string { - if !c.teamURLValid { - c.SetTeamURLFromSession() - if !c.teamURLValid { - mlog.Debug("Team URL accessed when not valid. Team URL should not be used in API functions or those that are team independent") - } - } - return c.teamURL -} - -func (c *Context) GetSiteURLHeader() string { - return c.siteURLHeader -} - -func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool { - metrics := c.App.Metrics - if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 { - if et == etag { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.WriteHeader(http.StatusNotModified) - if metrics != nil { - metrics.IncrementEtagHitCounter(routeName) - } - return true - } - } - - if metrics != nil { - metrics.IncrementEtagMissCounter(routeName) - } - - return false -} - -func IsApiCall(r *http.Request) bool { - return strings.Index(r.URL.Path, "/api/") == 0 -} - -func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) { - err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) - - mlog.Debug(fmt.Sprintf("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))) - - if IsApiCall(r) { - w.WriteHeader(err.StatusCode) - err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?" - w.Write([]byte(err.ToJson())) - } else { - utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) - } -} - -func (c *Context) CheckTeamId() { - if c.TeamId != "" && c.Session.GetTeamByTeamId(c.TeamId) == nil { - if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - if result := <-c.App.Srv.Store.Team().Get(c.TeamId); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusBadRequest - return - } - } else { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } -} diff --git a/api/context_test.go b/api/context_test.go deleted file mode 100644 index 95a8459ff..000000000 --- a/api/context_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestSiteURLHeader(t *testing.T) { - c := &Context{} - - testCases := []struct { - url string - want string - }{ - {"http://mattermost.com/", "http://mattermost.com"}, - {"http://mattermost.com", "http://mattermost.com"}, - } - - for _, tc := range testCases { - c.SetSiteURLHeader(tc.url) - - if c.siteURLHeader != tc.want { - t.Fatalf("expected %s, got %s", tc.want, c.siteURLHeader) - } - } - -} diff --git a/api/deprecated_test.go b/api/deprecated_test.go deleted file mode 100644 index 6943c6918..000000000 --- a/api/deprecated_test.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api diff --git a/api/emoji.go b/api/emoji.go deleted file mode 100644 index b366530ba..000000000 --- a/api/emoji.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "image" - "image/draw" - "image/gif" - "net/http" - "strings" - - "image/color/palette" - - "github.com/disintegration/imaging" - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitEmoji() { - api.BaseRoutes.Emoji.Handle("/list", api.ApiUserRequired(getEmoji)).Methods("GET") - api.BaseRoutes.Emoji.Handle("/create", api.ApiUserRequired(createEmoji)).Methods("POST") - api.BaseRoutes.Emoji.Handle("/delete", api.ApiUserRequired(deleteEmoji)).Methods("POST") - api.BaseRoutes.Emoji.Handle("/{id:[A-Za-z0-9_]+}", api.ApiUserRequiredTrustRequester(getEmojiImage)).Methods("GET") -} - -func getEmoji(c *Context, w http.ResponseWriter, r *http.Request) { - if !*c.App.Config().ServiceSettings.EnableCustomEmoji { - c.Err = model.NewAppError("getEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - listEmoji, err := c.App.GetEmojiList(0, 100000, "") - if err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.EmojiListToJson(listEmoji))) - } -} - -func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { - if !*c.App.Config().ServiceSettings.EnableCustomEmoji { - c.Err = model.NewAppError("createEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - if emojiInterface := c.App.Emoji; emojiInterface != nil && - !emojiInterface.CanUserCreateEmoji(c.Session.Roles, c.Session.TeamMembers) { - c.Err = model.NewAppError("createEmoji", "api.emoji.create.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized) - return - } - - if len(*c.App.Config().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", "emoji") - return - } - - // wipe the emoji id so that existing emojis can't get overwritten - emoji.Id = "" - - // do our best to validate the emoji before committing anything to the DB so that we don't have to clean up - // orphaned files left over when validation fails later on - emoji.PreSave() - if err := emoji.IsValid(); err != nil { - c.Err = err - c.Err.StatusCode = http.StatusBadRequest - return - } - - if emoji.CreatorId != c.Session.UserId { - c.Err = model.NewAppError("createEmoji", "api.emoji.create.other_user.app_error", nil, "", http.StatusUnauthorized) - return - } - - if result := <-c.App.Srv.Store.Emoji().GetByName(emoji.Name); result.Err == nil && result.Data != nil { - c.Err = model.NewAppError("createEmoji", "api.emoji.create.duplicate.app_error", nil, "", http.StatusBadRequest) - return - } - - if imageData := m.File["image"]; len(imageData) == 0 { - c.SetInvalidParam("createEmoji", "image") - return - } else if err := c.App.UploadEmojiImage(emoji.Id, imageData[0]); err != nil { - c.Err = err - return - } - - if result := <-c.App.Srv.Store.Emoji().Save(emoji); result.Err != nil { - c.Err = result.Err - return - } else { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil) - message.Add("emoji", result.Data.(*model.Emoji).ToJson()) - - c.App.Publish(message) - w.Write([]byte(result.Data.(*model.Emoji).ToJson())) - } -} - -func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) { - if !*c.App.Config().ServiceSettings.EnableCustomEmoji { - c.Err = model.NewAppError("deleteEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - if len(*c.App.Config().FileSettings.DriverName) == 0 { - c.Err = model.NewAppError("deleteImage", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) - return - } - - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteEmoji", "id") - return - } - - emoji, err := c.App.GetEmoji(id) - if err != nil { - c.Err = err - return - } - - if c.Session.UserId != emoji.CreatorId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.Err = model.NewAppError("deleteEmoji", "api.emoji.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized) - return - } - - err = c.App.DeleteEmoji(emoji) - if err != nil { - c.Err = err - return - } else { - ReturnStatusOK(w) - } -} - -func getEmojiImage(c *Context, w http.ResponseWriter, r *http.Request) { - if !*c.App.Config().ServiceSettings.EnableCustomEmoji { - c.Err = model.NewAppError("getEmojiImage", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - if len(*c.App.Config().FileSettings.DriverName) == 0 { - c.Err = model.NewAppError("getEmojiImage", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) - return - } - - params := mux.Vars(r) - - id := params["id"] - if len(id) == 0 { - c.SetInvalidParam("getEmojiImage", "id") - return - } - - image, imageType, err := c.App.GetEmojiImage(id) - if err != nil { - c.Err = err - return - } - - w.Header().Set("Content-Type", "image/"+imageType) - w.Header().Set("Cache-Control", "max-age=2592000, public") - w.Write(image) -} - -func resizeEmoji(img image.Image, width int, height int) image.Image { - emojiWidth := float64(width) - emojiHeight := float64(height) - - var emoji image.Image - if emojiHeight <= app.MaxEmojiHeight && emojiWidth <= app.MaxEmojiWidth { - emoji = img - } else { - emoji = imaging.Fit(img, app.MaxEmojiWidth, app.MaxEmojiHeight, imaging.Lanczos) - } - return emoji -} - -func resizeEmojiGif(gifImg *gif.GIF) *gif.GIF { - // Create a new RGBA image to hold the incremental frames. - firstFrame := gifImg.Image[0].Bounds() - b := image.Rect(0, 0, firstFrame.Dx(), firstFrame.Dy()) - img := image.NewRGBA(b) - - resizedImage := image.Image(nil) - // Resize each frame. - for index, frame := range gifImg.Image { - bounds := frame.Bounds() - draw.Draw(img, bounds, frame, bounds.Min, draw.Over) - resizedImage = resizeEmoji(img, firstFrame.Dx(), firstFrame.Dy()) - gifImg.Image[index] = imageToPaletted(resizedImage) - } - // Set new gif width and height - gifImg.Config.Width = resizedImage.Bounds().Dx() - gifImg.Config.Height = resizedImage.Bounds().Dy() - return gifImg -} - -func imageToPaletted(img image.Image) *image.Paletted { - b := img.Bounds() - pm := image.NewPaletted(b, palette.Plan9) - draw.FloydSteinberg.Draw(pm, b, img, image.ZP) - return pm -} diff --git a/api/emoji_test.go b/api/emoji_test.go deleted file mode 100644 index 108c416e1..000000000 --- a/api/emoji_test.go +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "image" - "image/gif" - "testing" - "time" - - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" -) - -func TestGetEmoji(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - EnableCustomEmoji := *th.App.Config().ServiceSettings.EnableCustomEmoji - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) - - emojis := []*model.Emoji{ - { - CreatorId: model.NewId(), - Name: model.NewId(), - }, - { - CreatorId: model.NewId(), - Name: model.NewId(), - }, - { - CreatorId: model.NewId(), - Name: model.NewId(), - }, - } - - for i, emoji := range emojis { - emojis[i] = store.Must(th.App.Srv.Store.Emoji().Save(emoji)).(*model.Emoji) - } - defer func() { - for _, emoji := range emojis { - store.Must(th.App.Srv.Store.Emoji().Delete(emoji.Id, time.Now().Unix())) - } - }() - - if returnedEmojis, err := Client.ListEmoji(); err != nil { - t.Fatal(err) - } else { - for _, emoji := range emojis { - found := false - - for _, savedEmoji := range returnedEmojis { - if emoji.Id == savedEmoji.Id { - found = true - break - } - } - - if !found { - t.Fatalf("failed to get emoji with id %v", emoji.Id) - } - } - } - - deleted := &model.Emoji{ - CreatorId: model.NewId(), - Name: model.NewId(), - DeleteAt: 1, - } - deleted = store.Must(th.App.Srv.Store.Emoji().Save(deleted)).(*model.Emoji) - - if returnedEmojis, err := Client.ListEmoji(); err != nil { - t.Fatal(err) - } else { - found := false - - for _, savedEmoji := range returnedEmojis { - if deleted.Id == savedEmoji.Id { - found = true - break - } - } - - if found { - t.Fatalf("shouldn't have gotten deleted emoji %v", deleted.Id) - } - } -} - -func TestCreateEmoji(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - EnableCustomEmoji := *th.App.Config().ServiceSettings.EnableCustomEmoji - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = false }) - - emoji := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - - // try to create an emoji when they're disabled - if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil { - t.Fatal("shouldn't be able to create an emoji when they're disabled") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) - - // try to create a valid gif emoji when they're enabled - if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err != nil { - t.Fatal(err) - } else { - emoji = emojiResult - } - - // try to create an emoji with a duplicate name - emoji2 := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: emoji.Name, - } - if _, err := Client.CreateEmoji(emoji2, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil { - t.Fatal("shouldn't be able to create an emoji with a duplicate name") - } - - Client.MustGeneric(Client.DeleteEmoji(emoji.Id)) - - // try to create a valid animated gif emoji - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif"); err != nil { - t.Fatal(err) - } else { - emoji = emojiResult - } - Client.MustGeneric(Client.DeleteEmoji(emoji.Id)) - - // try to create a valid jpeg emoji - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.jpeg"); err != nil { - t.Fatal(err) - } else { - emoji = emojiResult - } - Client.MustGeneric(Client.DeleteEmoji(emoji.Id)) - - // try to create a valid png emoji - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.png"); err != nil { - t.Fatal(err) - } else { - emoji = emojiResult - } - Client.MustGeneric(Client.DeleteEmoji(emoji.Id)) - - // try to create an emoji that's too wide - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif"); err != nil { - t.Fatal("should be able to create an emoji that's too wide by resizing it") - } - - // try to create an emoji that's too tall - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 1000), "image.gif"); err != nil { - t.Fatal("should be able to create an emoji that's too tall by resizing it") - } - - // try to create an emoji that's too large - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if _, err := Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif"); err == nil { - t.Fatal("shouldn't be able to create an emoji that's too large") - } - - // try to create an emoji with data that isn't an image - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if _, err := Client.CreateEmoji(emoji, make([]byte, 100), "image.gif"); err == nil { - t.Fatal("shouldn't be able to create an emoji with non-image data") - } - - // try to create an emoji as another user - emoji = &model.Emoji{ - CreatorId: th.BasicUser2.Id, - Name: model.NewId(), - } - if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil { - t.Fatal("shouldn't be able to create an emoji as another user") - } -} - -func TestDeleteEmoji(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - EnableCustomEmoji := *th.App.Config().ServiceSettings.EnableCustomEmoji - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = false }) - - emoji1 := createTestEmoji(t, th.App, &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - }, utils.CreateTestGif(t, 10, 10)) - - if _, err := Client.DeleteEmoji(emoji1.Id); err == nil { - t.Fatal("shouldn't have been able to delete an emoji when they're disabled") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) - - if deleted, err := Client.DeleteEmoji(emoji1.Id); err != nil { - t.Fatal(err) - } else if !deleted { - t.Fatalf("should be able to delete your own emoji %v", emoji1.Id) - } - - if _, err := Client.DeleteEmoji(emoji1.Id); err == nil { - t.Fatal("shouldn't be able to delete an already-deleted emoji") - } - - emoji2 := createTestEmoji(t, th.App, &model.Emoji{ - CreatorId: th.BasicUser2.Id, - Name: model.NewId(), - }, utils.CreateTestGif(t, 10, 10)) - - if _, err := Client.DeleteEmoji(emoji2.Id); err == nil { - t.Fatal("shouldn't be able to delete another user's emoji") - } - - if deleted, err := th.SystemAdminClient.DeleteEmoji(emoji2.Id); err != nil { - t.Fatal(err) - } else if !deleted { - t.Fatalf("system admin should be able to delete anyone's emoji %v", emoji2.Id) - } -} - -func createTestEmoji(t *testing.T, a *app.App, emoji *model.Emoji, imageData []byte) *model.Emoji { - emoji = store.Must(a.Srv.Store.Emoji().Save(emoji)).(*model.Emoji) - - if _, err := a.WriteFile(bytes.NewReader(imageData), "emoji/"+emoji.Id+"/image"); err != nil { - store.Must(a.Srv.Store.Emoji().Delete(emoji.Id, time.Now().Unix())) - t.Fatalf("failed to write image: %v", err.Error()) - } - - return emoji -} - -func TestGetEmojiImage(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - EnableCustomEmoji := *th.App.Config().ServiceSettings.EnableCustomEmoji - RestrictCustomEmojiCreation := *th.App.Config().ServiceSettings.RestrictCustomEmojiCreation - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.RestrictCustomEmojiCreation = RestrictCustomEmojiCreation - }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.RestrictCustomEmojiCreation = model.RESTRICT_EMOJI_CREATION_ALL - }) - - emoji1 := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - emoji1 = Client.MustGeneric(Client.CreateEmoji(emoji1, utils.CreateTestGif(t, 10, 10), "image.gif")).(*model.Emoji) - defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji1.Id)) }() - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = false }) - - if _, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji1.Id), "", ""); err == nil { - t.Fatal("should've failed to get emoji image when disabled") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) - - if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji1.Id), "", ""); err != nil { - t.Fatal(err) - } else if resp.Header.Get("Content-Type") != "image/gif" { - t.Fatal("should've received a gif") - } else if _, imageType, err := image.DecodeConfig(resp.Body); err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "gif" { - t.Fatal("should've received gif data") - } - - emoji2 := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - emoji2 = Client.MustGeneric(Client.CreateEmoji(emoji2, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif")).(*model.Emoji) - defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji2.Id)) }() - - if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji2.Id), "", ""); err != nil { - t.Fatal(err) - } else if resp.Header.Get("Content-Type") != "image/gif" { - t.Fatal("should've received a gif") - } else if _, imageType, err := image.DecodeConfig(resp.Body); err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "gif" { - t.Fatal("should've received gif data") - } - - emoji3 := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - emoji3 = Client.MustGeneric(Client.CreateEmoji(emoji3, utils.CreateTestJpeg(t, 10, 10), "image.jpeg")).(*model.Emoji) - defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji3.Id)) }() - - if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji3.Id), "", ""); err != nil { - t.Fatal(err) - } else if resp.Header.Get("Content-Type") != "image/jpeg" { - t.Fatal("should've received a jpeg") - } else if _, imageType, err := image.DecodeConfig(resp.Body); err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "jpeg" { - t.Fatal("should've received jpeg data") - } - - emoji4 := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - emoji4 = Client.MustGeneric(Client.CreateEmoji(emoji4, utils.CreateTestPng(t, 10, 10), "image.png")).(*model.Emoji) - defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji4.Id)) }() - - if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji4.Id), "", ""); err != nil { - t.Fatal(err) - } else if resp.Header.Get("Content-Type") != "image/png" { - t.Fatal("should've received a png") - } else if _, imageType, err := image.DecodeConfig(resp.Body); err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "png" { - t.Fatal("should've received png data") - } - - emoji5 := &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - emoji5 = Client.MustGeneric(Client.CreateEmoji(emoji5, utils.CreateTestPng(t, 10, 10), "image.png")).(*model.Emoji) - Client.MustGeneric(Client.DeleteEmoji(emoji5.Id)) - - if _, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji5.Id), "", ""); err == nil { - t.Fatal("should've failed to get image for deleted emoji") - } -} - -func TestResizeEmoji(t *testing.T) { - // try to resize a jpeg image within MaxEmojiWidth and MaxEmojiHeight - small_img_data := utils.CreateTestJpeg(t, app.MaxEmojiWidth, app.MaxEmojiHeight) - if small_img, _, err := image.Decode(bytes.NewReader(small_img_data)); err != nil { - t.Fatal("failed to decode jpeg bytes to image.Image") - } else { - resized_img := resizeEmoji(small_img, small_img.Bounds().Dx(), small_img.Bounds().Dy()) - if resized_img.Bounds().Dx() > app.MaxEmojiWidth || resized_img.Bounds().Dy() > app.MaxEmojiHeight { - t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") - } - if resized_img != small_img { - t.Fatal("should've returned small_img itself") - } - } - // try to resize a jpeg image - jpeg_data := utils.CreateTestJpeg(t, 256, 256) - if jpeg_img, _, err := image.Decode(bytes.NewReader(jpeg_data)); err != nil { - t.Fatal("failed to decode jpeg bytes to image.Image") - } else { - resized_jpeg := resizeEmoji(jpeg_img, jpeg_img.Bounds().Dx(), jpeg_img.Bounds().Dy()) - if resized_jpeg.Bounds().Dx() > app.MaxEmojiWidth || resized_jpeg.Bounds().Dy() > app.MaxEmojiHeight { - t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") - } - } - // try to resize a png image - png_data := utils.CreateTestJpeg(t, 256, 256) - if png_img, _, err := image.Decode(bytes.NewReader(png_data)); err != nil { - t.Fatal("failed to decode png bytes to image.Image") - } else { - resized_png := resizeEmoji(png_img, png_img.Bounds().Dx(), png_img.Bounds().Dy()) - if resized_png.Bounds().Dx() > app.MaxEmojiWidth || resized_png.Bounds().Dy() > app.MaxEmojiHeight { - t.Fatal("resized png width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") - } - } - // try to resize an animated gif - gif_data := utils.CreateTestAnimatedGif(t, 256, 256, 10) - if gif_img, err := gif.DecodeAll(bytes.NewReader(gif_data)); err != nil { - t.Fatal("failed to decode gif bytes to gif.GIF") - } else { - resized_gif := resizeEmojiGif(gif_img) - if resized_gif.Config.Width > app.MaxEmojiWidth || resized_gif.Config.Height > app.MaxEmojiHeight { - t.Fatal("resized gif width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight") - } - if len(resized_gif.Image) != len(gif_img.Image) { - t.Fatal("resized gif should have the same number of frames as original gif") - } - } -} diff --git a/api/file.go b/api/file.go deleted file mode 100644 index 63432eda5..000000000 --- a/api/file.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" -) - -const ( - PREVIEW_IMAGE_TYPE = "image/jpeg" - THUMBNAIL_IMAGE_TYPE = "image/jpeg" -) - -var UNSAFE_CONTENT_TYPES = [...]string{ - "application/javascript", - "application/ecmascript", - "text/javascript", - "text/ecmascript", - "application/x-javascript", - "text/html", -} - -func (api *API) InitFile() { - api.BaseRoutes.TeamFiles.Handle("/upload", api.ApiUserRequired(uploadFile)).Methods("POST") - - api.BaseRoutes.NeedFile.Handle("/get", api.ApiUserRequiredTrustRequester(getFile)).Methods("GET") - api.BaseRoutes.NeedFile.Handle("/get_thumbnail", api.ApiUserRequiredTrustRequester(getFileThumbnail)).Methods("GET") - api.BaseRoutes.NeedFile.Handle("/get_preview", api.ApiUserRequiredTrustRequester(getFilePreview)).Methods("GET") - api.BaseRoutes.NeedFile.Handle("/get_info", api.ApiUserRequired(getFileInfo)).Methods("GET") - api.BaseRoutes.NeedFile.Handle("/get_public_link", api.ApiUserRequired(getPublicLink)).Methods("GET") - - api.BaseRoutes.Public.Handle("/files/{file_id:[A-Za-z0-9]+}/get", api.ApiAppHandlerTrustRequesterIndependent(getPublicFile)).Methods("GET") - api.BaseRoutes.Public.Handle("/files/get/{team_id:[A-Za-z0-9]+}/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:(?:[A-Za-z0-9]+/)?.+(?:\\.[A-Za-z0-9]{3,})?}", api.ApiAppHandlerTrustRequesterIndependent(getPublicFileOld)).Methods("GET") -} - -func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { - if !*c.App.Config().FileSettings.EnableFileAttachments { - c.Err = model.NewAppError("uploadFile", "api.file.attachments.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize { - c.Err = model.NewAppError("uploadFile", "api.file.upload_file.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge) - return - } - - if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - m := r.MultipartForm - - props := m.Value - if len(props["channel_id"]) == 0 { - c.SetInvalidParam("uploadFile", "channel_id") - return - } - channelId := props["channel_id"][0] - if len(channelId) == 0 { - c.SetInvalidParam("uploadFile", "channel_id") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_UPLOAD_FILE) { - c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) - return - } - - resStruct, err := c.App.UploadMultipartFiles(c.TeamId, channelId, c.Session.UserId, m.File["files"], m.Value["client_ids"]) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(resStruct.ToJson())) -} - -func getFile(c *Context, w http.ResponseWriter, r *http.Request) { - info, err := getFileInfoForRequest(c, r, true) - if err != nil { - c.Err = err - return - } - - if data, err := c.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 { - c.Err = err - return - } -} - -func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { - info, err := getFileInfoForRequest(c, r, true) - if err != nil { - c.Err = err - return - } - - if info.ThumbnailPath == "" { - c.Err = model.NewAppError("getFileThumbnail", "api.file.get_file_thumbnail.no_thumbnail.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) - return - } - - if data, err := c.App.ReadFile(info.ThumbnailPath); err != nil { - c.Err = err - c.Err.StatusCode = http.StatusNotFound - } else if err := writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, data, w, r); err != nil { - c.Err = err - return - } -} - -func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) { - info, err := getFileInfoForRequest(c, r, true) - if err != nil { - c.Err = err - return - } - - if info.PreviewPath == "" { - c.Err = model.NewAppError("getFilePreview", "api.file.get_file_preview.no_preview.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) - return - } - - if data, err := c.App.ReadFile(info.PreviewPath); err != nil { - c.Err = err - c.Err.StatusCode = http.StatusNotFound - } else if err := writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, data, w, r); err != nil { - c.Err = err - return - } -} - -func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { - info, err := getFileInfoForRequest(c, r, true) - if err != nil { - c.Err = err - return - } - - w.Header().Set("Cache-Control", "max-age=2592000, public") - - w.Write([]byte(info.ToJson())) -} - -func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.Config().FileSettings.EnablePublicLink { - c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - info, err := getFileInfoForRequest(c, r, false) - if err != nil { - c.Err = err - return - } - - hash := r.URL.Query().Get("h") - - if len(hash) > 0 { - correctHash := app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt) - - if hash != correctHash { - c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) - return - } - } else { - c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) - return - } - - if data, err := c.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 { - c.Err = err - return - } -} - -func getFileInfoForRequest(c *Context, r *http.Request, requireFileVisible bool) (*model.FileInfo, *model.AppError) { - if len(*c.App.Config().FileSettings.DriverName) == 0 { - return nil, model.NewAppError("getFileInfoForRequest", "api.file.get_info_for_request.storage.app_error", nil, "", http.StatusNotImplemented) - } - - params := mux.Vars(r) - - fileId := params["file_id"] - if len(fileId) != 26 { - return nil, NewInvalidParamError("getFileInfoForRequest", "file_id") - } - - info, err := c.App.GetFileInfo(fileId) - if err != nil { - return nil, err - } - - // only let users access files visible in a channel, unless they're the one who uploaded the file - if info.CreatorId != c.Session.UserId { - if len(info.PostId) == 0 { - err := model.NewAppError("getFileInfoForRequest", "api.file.get_file_info_for_request.no_post.app_error", nil, "file_id="+fileId, http.StatusBadRequest) - return nil, err - } - - if requireFileVisible { - if !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return nil, c.Err - } - } - } - - return info, nil -} - -func getPublicFileOld(c *Context, w http.ResponseWriter, r *http.Request) { - if len(*c.App.Config().FileSettings.DriverName) == 0 { - c.Err = model.NewAppError("getPublicFile", "api.file.get_public_file_old.storage.app_error", nil, "", http.StatusNotImplemented) - return - } else if !c.App.Config().FileSettings.EnablePublicLink { - c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - params := mux.Vars(r) - - teamId := params["team_id"] - channelId := params["channel_id"] - userId := params["user_id"] - filename := params["filename"] - - hash := r.URL.Query().Get("h") - - if len(hash) > 0 { - correctHash := app.GeneratePublicLinkHash(filename, *c.App.Config().FileSettings.PublicLinkSalt) - - if hash != correctHash { - c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) - return - } - } else { - c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) - return - } - - path := "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename - - var info *model.FileInfo - if result := <-c.App.Srv.Store.FileInfo().GetByPath(path); result.Err != nil { - c.Err = result.Err - return - } else { - info = result.Data.(*model.FileInfo) - } - - if len(info.PostId) == 0 { - c.Err = model.NewAppError("getPublicFileOld", "api.file.get_public_file_old.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) - return - } - - if data, err := c.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 { - c.Err = err - return - } -} - -func writeFileResponse(filename string, contentType string, bytes []byte, w http.ResponseWriter, r *http.Request) *model.AppError { - w.Header().Set("Cache-Control", "max-age=2592000, private") - w.Header().Set("Content-Length", strconv.Itoa(len(bytes))) - w.Header().Set("X-Content-Type-Options", "nosniff") - - if contentType == "" { - contentType = "application/octet-stream" - } else { - for _, unsafeContentType := range UNSAFE_CONTENT_TYPES { - if strings.HasPrefix(contentType, unsafeContentType) { - contentType = "text/plain" - break - } - } - } - - w.Header().Set("Content-Type", contentType) - - w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+url.QueryEscape(filename)) - - // prevent file links from being embedded in iframes - w.Header().Set("X-Frame-Options", "DENY") - w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'") - - w.Write(bytes) - - return nil -} - -func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.Config().FileSettings.EnablePublicLink { - c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - info, err := getFileInfoForRequest(c, r, true) - if err != nil { - c.Err = err - return - } - - if len(info.PostId) == 0 { - c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) - return - } - - w.Write([]byte(model.StringToJson(c.App.GeneratePublicLinkV3(c.GetSiteURLHeader(), info)))) -} diff --git a/api/file_test.go b/api/file_test.go deleted file mode 100644 index 7a04674cd..000000000 --- a/api/file_test.go +++ /dev/null @@ -1,950 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" - - s3 "github.com/minio/minio-go" - "github.com/minio/minio-go/pkg/credentials" -) - -func TestUploadFile(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Logf("skipping because no file driver is enabled") - return - } - - Client := th.BasicClient - team := th.BasicTeam - user := th.BasicUser - channel := th.BasicChannel - - var uploadInfo *model.FileInfo - var data []byte - var err error - if data, err = readTestFile("test.png"); err != nil { - t.Fatal(err) - } else if resp, err := Client.UploadPostAttachment(data, channel.Id, "test.png"); err != nil { - t.Fatal(err) - } else if len(resp.FileInfos) != 1 { - t.Fatal("should've returned a single file infos") - } else { - uploadInfo = resp.FileInfos[0] - } - - // The returned file info from the upload call will be missing some fields that will be stored in the database - if uploadInfo.CreatorId != user.Id { - t.Fatal("file should be assigned to user") - } else if uploadInfo.PostId != "" { - t.Fatal("file shouldn't have a post") - } else if uploadInfo.Path != "" { - t.Fatal("file path should not be set on returned info") - } else if uploadInfo.ThumbnailPath != "" { - t.Fatal("file thumbnail path should not be set on returned info") - } else if uploadInfo.PreviewPath != "" { - t.Fatal("file preview path should not be set on returned info") - } - - var info *model.FileInfo - if result := <-th.App.Srv.Store.FileInfo().Get(uploadInfo.Id); result.Err != nil { - t.Fatal(result.Err) - } else { - info = result.Data.(*model.FileInfo) - } - - if info.Id != uploadInfo.Id { - t.Fatal("file id from response should match one stored in database") - } else if info.CreatorId != user.Id { - t.Fatal("file should be assigned to user") - } else if info.PostId != "" { - t.Fatal("file shouldn't have a post") - } else if info.Path == "" { - t.Fatal("file path should be set in database") - } else if info.ThumbnailPath == "" { - t.Fatal("file thumbnail path should be set in database") - } else if info.PreviewPath == "" { - t.Fatal("file preview path should be set in database") - } - - date := time.Now().Format("20060102") - - // This also makes sure that the relative path provided above is sanitized out - expectedPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test.png", date, team.Id, channel.Id, user.Id, info.Id) - if info.Path != expectedPath { - t.Logf("file is saved in %v", info.Path) - t.Fatalf("file should've been saved in %v", expectedPath) - } - - expectedThumbnailPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test_thumb.jpg", date, team.Id, channel.Id, user.Id, info.Id) - if info.ThumbnailPath != expectedThumbnailPath { - t.Logf("file thumbnail is saved in %v", info.ThumbnailPath) - t.Fatalf("file thumbnail should've been saved in %v", expectedThumbnailPath) - } - - expectedPreviewPath := fmt.Sprintf("%v/teams/%v/channels/%v/users/%v/%v/test_preview.jpg", date, team.Id, channel.Id, user.Id, info.Id) - if info.PreviewPath != expectedPreviewPath { - t.Logf("file preview is saved in %v", info.PreviewPath) - t.Fatalf("file preview should've been saved in %v", expectedPreviewPath) - } - - if _, err := Client.UploadPostAttachment(data, model.NewId(), "test.png"); err == nil || err.StatusCode != http.StatusForbidden { - t.Fatal("should have failed - bad channel id") - } - - if _, err := Client.UploadPostAttachment(data, "../../junk", "test.png"); err == nil || err.StatusCode != http.StatusForbidden { - t.Fatal("should have failed - bad channel id") - } - - if _, err := th.SystemAdminClient.UploadPostAttachment(data, model.NewId(), "test.png"); err == nil || err.StatusCode != http.StatusForbidden { - t.Fatal("should have failed - bad channel id") - } - - if _, err := th.SystemAdminClient.UploadPostAttachment(data, "../../junk", "test.png"); err == nil || err.StatusCode != http.StatusForbidden { - t.Fatal("should have failed - bad channel id") - } - - enableFileAttachments := *th.App.Config().FileSettings.EnableFileAttachments - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = enableFileAttachments }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = false }) - - if data, err := readTestFile("test.png"); err != nil { - t.Fatal(err) - } else if _, err = Client.UploadPostAttachment(data, channel.Id, "test.png"); err == nil { - t.Fatal("should have errored") - } - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if err := cleanupTestFile(info, &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestGetFileInfo(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - user := th.BasicUser - channel := th.BasicChannel - - var fileId string - if data, err := readTestFile("test.png"); err != nil { - t.Fatal(err) - } else { - fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - - info, err := Client.GetFileInfo(fileId) - if err != nil { - t.Fatal(err) - } else if info.Id != fileId { - t.Fatal("got incorrect file") - } else if info.CreatorId != user.Id { - t.Fatal("file should be assigned to user") - } else if info.PostId != "" { - t.Fatal("file shouldn't have a post") - } else if info.Path != "" { - t.Fatal("file path shouldn't have been returned to client") - } else if info.ThumbnailPath != "" { - t.Fatal("file thumbnail path shouldn't have been returned to client") - } else if info.PreviewPath != "" { - t.Fatal("file preview path shouldn't have been returned to client") - } else if info.MimeType != "image/png" { - t.Fatal("mime type should've been image/png") - } - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - // Other user shouldn't be able to get file info for this file before it's attached to a post - th.LoginBasic2() - - if _, err := Client.GetFileInfo(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file info before it's attached to a post") - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - // Other user shouldn't be able to get file info for this file if they're not in the channel for it - if _, err := Client.GetFileInfo(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file info when not in channel") - } - - Client.Must(Client.JoinChannel(channel.Id)) - - // Other user should now be able to get file info - if info2, err := Client.GetFileInfo(fileId); err != nil { - t.Fatal(err) - } else if info2.Id != fileId { - t.Fatal("other user got incorrect file") - } - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestGetFile(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - channel := th.BasicChannel - - var fileId string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if body, err := Client.GetFile(fileId); err != nil { - t.Fatal(err) - } else { - received, err := ioutil.ReadAll(body) - if err != nil { - t.Fatal(err) - } else if len(received) != len(data) { - t.Fatal("received file should be the same size as the sent one") - } - - for i := range data { - if data[i] != received[i] { - t.Fatal("received file didn't match sent one") - } - } - - body.Close() - } - - // Other user shouldn't be able to get file for this file before it's attached to a post - th.LoginBasic2() - - if _, err := Client.GetFile(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file before it's attached to a post") - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - // Other user shouldn't be able to get file for this file if they're not in the channel for it - if _, err := Client.GetFile(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file when not in channel") - } - - Client.Must(Client.JoinChannel(channel.Id)) - - // Other user should now be able to get file - if body, err := Client.GetFile(fileId); err != nil { - t.Fatal(err) - } else { - received, err := ioutil.ReadAll(body) - if err != nil { - t.Fatal(err) - } else if len(received) != len(data) { - t.Fatal("received file should be the same size as the sent one") - } - - for i := range data { - if data[i] != received[i] { - t.Fatal("received file didn't match sent one") - } - } - - body.Close() - } - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestGetFileThumbnail(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - channel := th.BasicChannel - - var fileId string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if body, err := Client.GetFileThumbnail(fileId); err != nil { - t.Fatal(err) - } else { - body.Close() - } - - // Other user shouldn't be able to get thumbnail for this file before it's attached to a post - th.LoginBasic2() - - if _, err := Client.GetFileThumbnail(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file before it's attached to a post") - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - // Other user shouldn't be able to get thumbnail for this file if they're not in the channel for it - if _, err := Client.GetFileThumbnail(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file when not in channel") - } - - Client.Must(Client.JoinChannel(channel.Id)) - - // Other user should now be able to get thumbnail - if body, err := Client.GetFileThumbnail(fileId); err != nil { - t.Fatal(err) - } else { - body.Close() - } - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestGetFilePreview(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - channel := th.BasicChannel - - var fileId string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if body, err := Client.GetFilePreview(fileId); err != nil { - t.Fatal(err) - } else { - body.Close() - } - - // Other user shouldn't be able to get preview for this file before it's attached to a post - th.LoginBasic2() - - if _, err := Client.GetFilePreview(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file before it's attached to a post") - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - // Other user shouldn't be able to get preview for this file if they're not in the channel for it - if _, err := Client.GetFilePreview(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file when not in channel") - } - - Client.Must(Client.JoinChannel(channel.Id)) - - // Other user should now be able to get preview - if body, err := Client.GetFilePreview(fileId); err != nil { - t.Fatal(err) - } else { - body.Close() - } - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestGetPublicFile(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - enablePublicLink := th.App.Config().FileSettings.EnablePublicLink - publicLinkSalt := *th.App.Config().FileSettings.PublicLinkSalt - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = enablePublicLink }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = publicLinkSalt }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewId() }) - - Client := th.BasicClient - channel := th.BasicChannel - - var fileId string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - link := Client.MustGeneric(Client.GetPublicLink(fileId)).(string) - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if resp, err := http.Get(link); err != nil || resp.StatusCode != http.StatusOK { - t.Log(link) - t.Fatal("failed to get image with public link", err) - } - - if resp, err := http.Get(link[:strings.LastIndex(link, "?")]); err == nil && resp.StatusCode != http.StatusBadRequest { - t.Fatal("should've failed to get image with public link without hash", resp.Status) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = false }) - if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusNotImplemented { - t.Fatal("should've failed to get image with disabled public link") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = true }) - - // test after the salt has changed - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewId() }) - - if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest { - t.Fatal("should've failed to get image with public link after salt changed") - } - - if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest { - t.Fatal("should've failed to get image with public link after salt changed") - } - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestGetPublicFileOld(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - enablePublicLink := th.App.Config().FileSettings.EnablePublicLink - publicLinkSalt := *th.App.Config().FileSettings.PublicLinkSalt - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = enablePublicLink }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = publicLinkSalt }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewId() }) - - channel := th.BasicChannel - - var fileId string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - //fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - fileId = model.NewId() - fileInfo := model.FileInfo{ - Id: fileId, - CreateAt: model.GetMillis(), - CreatorId: th.BasicUser.Id, - Path: fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", th.BasicTeam.Id, channel.Id, th.BasicUser.Id, fileId, "test.png"), - } - store.Must(th.App.Srv.Store.FileInfo().Save(&fileInfo)) - uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", th.BasicTeam.Id, channel.Id, th.BasicUser.Id, fileId), "test.png") - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - // reconstruct old style of link - siteURL := fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port) - link := generatePublicLinkOld(siteURL, th.BasicTeam.Id, channel.Id, th.BasicUser.Id, fileId+"/test.png", *th.App.Config().FileSettings.PublicLinkSalt) - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if resp, err := http.Get(link); err != nil || resp.StatusCode != http.StatusOK { - t.Fatalf("failed to get image with public link err=%v resp=%v", err, resp) - } - - if resp, err := http.Get(link[:strings.LastIndex(link, "?")]); err == nil && resp.StatusCode != http.StatusBadRequest { - t.Fatal("should've failed to get image with public link without hash", resp.Status) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = false }) - if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusNotImplemented { - t.Fatal("should've failed to get image with disabled public link") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = true }) - - // test after the salt has changed - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewId() }) - - if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest { - t.Fatal("should've failed to get image with public link after salt changed") - } - - if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest { - t.Fatal("should've failed to get image with public link after salt changed") - } - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func generatePublicLinkOld(siteURL, teamId, channelId, userId, filename, salt string) string { - hash := app.GeneratePublicLinkHash(filename, salt) - return fmt.Sprintf("%s%s/public/files/get/%s/%s/%s/%s?h=%s", siteURL, model.API_URL_SUFFIX_V3, teamId, channelId, userId, filename, hash) -} - -func TestGetPublicLink(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - enablePublicLink := th.App.Config().FileSettings.EnablePublicLink - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = enablePublicLink }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = true }) - - Client := th.BasicClient - channel := th.BasicChannel - - var fileId string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId = Client.MustGeneric(Client.UploadPostAttachment(data, channel.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - - if _, err := Client.GetPublicLink(fileId); err == nil { - t.Fatal("should've failed to get public link before file is attached to a post") - } - - // Hacky way to assign file to a post (usually would be done by CreatePost call) - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id)) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = false }) - - if _, err := Client.GetPublicLink(fileId); err == nil { - t.Fatal("should've failed to get public link when disabled") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.FileSettings.EnablePublicLink = true }) - - if link, err := Client.GetPublicLink(fileId); err != nil { - t.Fatal(err) - } else if link == "" { - t.Fatal("should've received public link") - } - - // Other user shouldn't be able to get public link for this file if they're not in the channel for it - th.LoginBasic2() - - if _, err := Client.GetPublicLink(fileId); err == nil { - t.Fatal("other user shouldn't be able to get file when not in channel") - } - - Client.Must(Client.JoinChannel(channel.Id)) - - // Other user should now be able to get public link - if link, err := Client.GetPublicLink(fileId); err != nil { - t.Fatal(err) - } else if link == "" { - t.Fatal("should've received public link") - } - - // Wait a bit for files to ready - time.Sleep(2 * time.Second) - - if err := cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo), &th.App.Config().FileSettings); err != nil { - t.Fatal(err) - } -} - -func TestMigrateFilenamesToFileInfos(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - - user1 := th.BasicUser - - channel1 := Client.Must(Client.CreateChannel(&model.Channel{ - Name: model.NewId(), - Type: model.CHANNEL_OPEN, - // No TeamId set to simulate a direct channel - })).Data.(*model.Channel) - - var fileId1 string - var fileId2 string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId1 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", th.BasicTeam.Id, channel1.Id, user1.Id, fileId1), "test.png") - fileId2 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", th.BasicTeam.Id, channel1.Id, user1.Id, fileId2), "test.png") - } - - // Bypass the Client whenever possible since we're trying to simulate a pre-3.5 post - post1 := store.Must(th.App.Srv.Store.Post().Save(&model.Post{ - UserId: user1.Id, - ChannelId: channel1.Id, - Message: "test", - Filenames: []string{ - fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId1, "test.png"), - fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId2, "test.png"), - fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId2, "test.png"), // duplicate a filename to recreate a rare bug - }, - })).(*model.Post) - - if post1.FileIds != nil && len(post1.FileIds) > 0 { - t.Fatal("post shouldn't have file ids") - } else if post1.Filenames == nil || len(post1.Filenames) != 3 { - t.Fatal("post should have filenames") - } - - // Indirectly call migrateFilenamesToFileInfos by calling Client.GetFileInfosForPost - var infos []*model.FileInfo - if infosResult, err := Client.GetFileInfosForPost(post1.ChannelId, post1.Id, ""); err != nil { - t.Fatal(err) - } else { - infos = infosResult - } - - if len(infos) != 2 { - t.Log(infos) - t.Fatal("should've had 2 infos after migration") - } else if infos[0].Path != "" || infos[0].ThumbnailPath != "" || infos[0].PreviewPath != "" { - t.Fatal("shouldn't return paths to client") - } - - // Should be able to get files after migration - if body, err := Client.GetFile(infos[0].Id); err != nil { - t.Fatal(err) - } else { - body.Close() - } - - if body, err := Client.GetFile(infos[1].Id); err != nil { - t.Fatal(err) - } else { - body.Close() - } - - // Make sure we aren't generating a new set of FileInfos on a second call to GetFileInfosForPost - if infos2 := Client.MustGeneric(Client.GetFileInfosForPost(post1.ChannelId, post1.Id, "")).([]*model.FileInfo); len(infos2) != len(infos) { - t.Fatal("should've received the same 2 infos after second call") - } else if (infos[0].Id != infos2[0].Id && infos[0].Id != infos2[1].Id) || (infos[1].Id != infos2[0].Id && infos[1].Id != infos2[1].Id) { - t.Fatal("should've returned the exact same 2 infos after second call") - } - - if result, err := Client.GetPost(post1.ChannelId, post1.Id, ""); err != nil { - t.Fatal(err) - } else if post := result.Data.(*model.PostList).Posts[post1.Id]; len(post.Filenames) != 0 { - t.Fatal("post shouldn't have filenames") - } else if len(post.FileIds) != 2 { - t.Fatal("post should have 2 file ids") - } else if (infos[0].Id != post.FileIds[0] && infos[0].Id != post.FileIds[1]) || (infos[1].Id != post.FileIds[0] && infos[1].Id != post.FileIds[1]) { - t.Fatal("post file ids should match GetFileInfosForPost results") - } -} - -func uploadFileOld(t *testing.T, data []byte, dest string, filename string) { - os.MkdirAll(dest, os.ModePerm) - eFile, err := os.Create(dest + "/" + filename) - if err != nil { - t.Fatal(err) - } - defer eFile.Close() - - _, err = io.Copy(eFile, bytes.NewReader(data)) // first var shows number of bytes - if err != nil { - t.Fatal(err) - } - - err = eFile.Sync() - if err != nil { - t.Fatal(err) - } -} - -func TestFindTeamIdForFilename(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - - user1 := th.BasicUser - - team1 := th.BasicTeam - team2 := th.CreateTeam(th.BasicClient) - - channel1 := th.BasicChannel - - Client.SetTeamId(team2.Id) - channel2 := Client.Must(Client.CreateChannel(&model.Channel{ - Name: model.NewId(), - Type: model.CHANNEL_OPEN, - // No TeamId set to simulate a direct channel - })).Data.(*model.Channel) - Client.SetTeamId(team1.Id) - - var fileId1 string - var fileId2 string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId1 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", team1.Id, channel1.Id, user1.Id, fileId1), "test.png") - - Client.SetTeamId(team2.Id) - fileId2 = Client.MustGeneric(Client.UploadPostAttachment(data, channel2.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", team2.Id, channel2.Id, user1.Id, fileId2), "test.png") - Client.SetTeamId(team1.Id) - } - - // Bypass the Client whenever possible since we're trying to simulate a pre-3.5 post - post1 := store.Must(th.App.Srv.Store.Post().Save(&model.Post{ - UserId: user1.Id, - ChannelId: channel1.Id, - Message: "test", - Filenames: []string{fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId1, "test.png")}, - })).(*model.Post) - - if teamId := th.App.FindTeamIdForFilename(post1, post1.Filenames[0]); teamId != team1.Id { - t.Log(teamId) - t.Fatal("file should've been found under team1") - } - - Client.SetTeamId(team2.Id) - post2 := store.Must(th.App.Srv.Store.Post().Save(&model.Post{ - UserId: user1.Id, - ChannelId: channel2.Id, - Message: "test", - Filenames: []string{fmt.Sprintf("/%s/%s/%s/%s", channel2.Id, user1.Id, fileId2, "test.png")}, - })).(*model.Post) - Client.SetTeamId(team1.Id) - - if teamId := th.App.FindTeamIdForFilename(post2, post2.Filenames[0]); teamId != team2.Id { - t.Fatal("file should've been found under team2") - } -} - -func TestGetInfoForFilename(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if *th.App.Config().FileSettings.DriverName == "" { - t.Skip("skipping because no file driver is enabled") - } - - Client := th.BasicClient - - user1 := th.BasicUser - - team1 := th.BasicTeam - - channel1 := th.BasicChannel - - var fileId1 string - var path string - var thumbnailPath string - var previewPath string - data, err := readTestFile("test.png") - if err != nil { - t.Fatal(err) - } else { - fileId1 = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - uploadFileOld(t, data, fmt.Sprintf("data/teams/%s/channels/%s/users/%s/%s", team1.Id, channel1.Id, user1.Id, fileId1), "test.png") - path = store.Must(th.App.Srv.Store.FileInfo().Get(fileId1)).(*model.FileInfo).Path - thumbnailPath = store.Must(th.App.Srv.Store.FileInfo().Get(fileId1)).(*model.FileInfo).ThumbnailPath - previewPath = store.Must(th.App.Srv.Store.FileInfo().Get(fileId1)).(*model.FileInfo).PreviewPath - } - - // Bypass the Client whenever possible since we're trying to simulate a pre-3.5 post - post1 := store.Must(th.App.Srv.Store.Post().Save(&model.Post{ - UserId: user1.Id, - ChannelId: channel1.Id, - Message: "test", - Filenames: []string{fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId1, "test.png")}, - })).(*model.Post) - - date := time.Now().Format("20060102") - - if info := th.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") - } else if info.CreatorId != user1.Id { - t.Fatal("incorrect user id") - } else if info.PostId != post1.Id { - t.Fatal("incorrect user id") - } else if fmt.Sprintf("%s/%s", date, info.Path) != path { - t.Fatal("incorrect path") - } else if fmt.Sprintf("%s/%s", date, info.ThumbnailPath) != thumbnailPath { - t.Fatal("incorrect thumbnail path") - } else if fmt.Sprintf("%s/%s", date, info.PreviewPath) != previewPath { - t.Fatal("incorrect preview path") - } else if info.Name != "test.png" { - t.Fatal("incorrect name") - } -} - -func readTestFile(name string) ([]byte, error) { - path, _ := utils.FindDir("tests") - file, err := os.Open(filepath.Join(path, name)) - if err != nil { - return nil, err - } - defer file.Close() - - data := &bytes.Buffer{} - if _, err := io.Copy(data, file); err != nil { - return nil, err - } else { - return data.Bytes(), nil - } -} - -// Similar to s3.New() but allows initialization of signature v2 or signature v4 client. -// If signV2 input is false, function always returns signature v4. -// -// Additionally this function also takes a user defined region, if set -// disables automatic region lookup. -func s3New(endpoint, accessKey, secretKey string, secure bool, signV2 bool, region string) (*s3.Client, error) { - var creds *credentials.Credentials - if signV2 { - creds = credentials.NewStatic(accessKey, secretKey, "", credentials.SignatureV2) - } else { - creds = credentials.NewStatic(accessKey, secretKey, "", credentials.SignatureV4) - } - return s3.NewWithCredentials(endpoint, creds, secure, region) -} - -func cleanupTestFile(info *model.FileInfo, settings *model.FileSettings) error { - if *settings.DriverName == model.IMAGE_DRIVER_S3 { - endpoint := settings.AmazonS3Endpoint - accessKey := settings.AmazonS3AccessKeyId - secretKey := settings.AmazonS3SecretAccessKey - secure := *settings.AmazonS3SSL - signV2 := *settings.AmazonS3SignV2 - region := settings.AmazonS3Region - s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2, region) - if err != nil { - return err - } - bucket := settings.AmazonS3Bucket - if err := s3Clnt.RemoveObject(bucket, info.Path); err != nil { - return err - } - - if info.ThumbnailPath != "" { - if err := s3Clnt.RemoveObject(bucket, info.ThumbnailPath); err != nil { - return err - } - } - - if info.PreviewPath != "" { - if err := s3Clnt.RemoveObject(bucket, info.PreviewPath); err != nil { - return err - } - } - } else if *settings.DriverName == model.IMAGE_DRIVER_LOCAL { - if err := os.Remove(settings.Directory + info.Path); err != nil { - return err - } - - if info.ThumbnailPath != "" { - if err := os.Remove(settings.Directory + info.ThumbnailPath); err != nil { - return err - } - } - - if info.PreviewPath != "" { - if err := os.Remove(settings.Directory + info.PreviewPath); err != nil { - return err - } - } - } - - return nil -} diff --git a/api/general.go b/api/general.go deleted file mode 100644 index 51c491526..000000000 --- a/api/general.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "net/http" - "strings" - - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitGeneral() { - api.BaseRoutes.General.Handle("/client_props", api.ApiAppHandler(getClientConfig)).Methods("GET") - api.BaseRoutes.General.Handle("/log_client", api.ApiAppHandler(logClient)).Methods("POST") - api.BaseRoutes.General.Handle("/ping", api.ApiAppHandler(ping)).Methods("GET") -} - -func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) { - w.Write([]byte(model.MapToJson(c.App.ClientConfig()))) -} - -func logClient(c *Context, w http.ResponseWriter, r *http.Request) { - forceToDebug := false - - if !*c.App.Config().ServiceSettings.EnableDeveloper { - if c.Session.UserId == "" { - c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "", http.StatusForbidden) - return - } - - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - forceToDebug = true - } - } - - m := model.MapFromJson(r.Body) - - lvl := m["level"] - msg := m["message"] - - // filter out javascript errors from franz that are polluting the log files - if strings.Contains(msg, "/franz") { - forceToDebug = true - } - - if len(msg) > 400 { - msg = msg[0:399] - } - - if lvl == "ERROR" { - err := &model.AppError{} - err.Message = msg - err.Id = msg - err.Where = "client" - - if forceToDebug { - c.LogDebug(err) - } else { - c.LogError(err) - } - } - - ReturnStatusOK(w) -} - -func ping(c *Context, w http.ResponseWriter, r *http.Request) { - m := make(map[string]string) - m["version"] = model.CurrentVersion - m["server_time"] = fmt.Sprintf("%v", model.GetMillis()) - w.Write([]byte(model.MapToJson(m))) -} diff --git a/api/general_test.go b/api/general_test.go deleted file mode 100644 index 5b0a2630f..000000000 --- a/api/general_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestGetClientProperties(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if props, err := th.BasicClient.GetClientProperties(); err != nil { - t.Fatal(err) - } else { - if len(props["Version"]) == 0 { - t.Fatal() - } - } -} - -func TestLogClient(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if ret, _ := th.BasicClient.LogClient("this is a test"); !ret { - t.Fatal("failed to log") - } - - enableDeveloper := *th.App.Config().ServiceSettings.EnableDeveloper - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = enableDeveloper }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = false }) - - th.BasicClient.Logout() - - if _, err := th.BasicClient.LogClient("this is a test"); err == nil { - t.Fatal("should have failed") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true }) - - if ret, _ := th.BasicClient.LogClient("this is a test"); !ret { - t.Fatal("failed to log") - } -} - -func TestGetPing(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if m, err := th.BasicClient.GetPing(); err != nil { - t.Fatal(err) - } else { - if len(m["version"]) == 0 { - t.Fatal() - } - } -} diff --git a/api/license.go b/api/license.go deleted file mode 100644 index 432442ad6..000000000 --- a/api/license.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "io" - "net/http" - - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitLicense() { - api.BaseRoutes.License.Handle("/add", api.ApiAdminSystemRequired(addLicense)).Methods("POST") - api.BaseRoutes.License.Handle("/remove", api.ApiAdminSystemRequired(removeLicense)).Methods("POST") - api.BaseRoutes.License.Handle("/client_config", api.ApiAppHandler(getClientLicenceConfig)).Methods("GET") -} - -func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { - c.LogAudit("attempt") - err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - m := r.MultipartForm - - fileArray, ok := m.File["license"] - if !ok { - c.Err = model.NewAppError("addLicense", "api.license.add_license.no_file.app_error", nil, "", http.StatusBadRequest) - return - } - - if len(fileArray) <= 0 { - c.Err = model.NewAppError("addLicense", "api.license.add_license.array.app_error", nil, "", http.StatusBadRequest) - return - } - - fileData := fileArray[0] - - file, err := fileData.Open() - if err != nil { - c.Err = model.NewAppError("addLicense", "api.license.add_license.open.app_error", nil, err.Error(), http.StatusInternalServerError) - return - } - defer file.Close() - - buf := bytes.NewBuffer(nil) - io.Copy(buf, file) - - if license, err := c.App.SaveLicense(buf.Bytes()); err != nil { - if err.Id == model.EXPIRED_LICENSE_ERROR { - c.LogAudit("failed - expired or non-started license") - } else if err.Id == model.INVALID_LICENSE_ERROR { - c.LogAudit("failed - invalid license") - } else { - c.LogAudit("failed - unable to save license") - } - c.Err = err - return - } else { - c.LogAudit("success") - w.Write([]byte(license.ToJson())) - } -} - -func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) { - c.LogAudit("") - - if err := c.App.RemoveLicense(); err != nil { - c.Err = err - return - } - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func getClientLicenceConfig(c *Context, w http.ResponseWriter, r *http.Request) { - useSanitizedLicense := !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) - - etag := c.App.GetClientLicenseEtag(useSanitizedLicense) - if c.HandleEtag(etag, "Get Client License Config", w, r) { - return - } - - var clientLicense map[string]string - - if useSanitizedLicense { - clientLicense = c.App.ClientLicense() - } else { - clientLicense = c.App.GetSanitizedClientLicense() - } - - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Write([]byte(model.MapToJson(clientLicense))) -} diff --git a/api/license_test.go b/api/license_test.go deleted file mode 100644 index 47586151a..000000000 --- a/api/license_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" -) - -func TestGetLicenceConfig(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if result, err := Client.GetClientLicenceConfig(""); err != nil { - t.Fatal(err) - } else { - cfg := result.Data.(map[string]string) - - if _, ok := cfg["IsLicensed"]; !ok { - t.Fatal(cfg) - } - - // test etag caching - if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { - t.Fatal(err) - } else if len(cache_result.Data.(map[string]string)) != 0 { - t.Log(cache_result.Data) - t.Fatal("cache should be empty") - } - - th.App.SetClientLicense(map[string]string{"IsLicensed": "true"}) - - if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { - t.Fatal(err) - } else if len(cache_result.Data.(map[string]string)) == 0 { - t.Fatal("result should not be empty") - } - - th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) - - if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { - t.Fatal(err) - } else if len(cache_result.Data.(map[string]string)) == 0 { - t.Fatal("result should not be empty") - } - - th.App.SetClientLicense(map[string]string{"IsLicensed": "false"}) - } -} diff --git a/api/oauth.go b/api/oauth.go deleted file mode 100644 index 2fc83d122..000000000 --- a/api/oauth.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitOAuth() { - api.BaseRoutes.OAuth.Handle("/register", api.ApiUserRequired(registerOAuthApp)).Methods("POST") - api.BaseRoutes.OAuth.Handle("/list", api.ApiUserRequired(getOAuthApps)).Methods("GET") - api.BaseRoutes.OAuth.Handle("/app/{client_id}", api.ApiUserRequired(getOAuthAppInfo)).Methods("GET") - api.BaseRoutes.OAuth.Handle("/allow", api.ApiUserRequired(allowOAuth)).Methods("GET") - api.BaseRoutes.OAuth.Handle("/authorized", api.ApiUserRequired(getAuthorizedApps)).Methods("GET") - api.BaseRoutes.OAuth.Handle("/delete", api.ApiUserRequired(deleteOAuthApp)).Methods("POST") - api.BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/deauthorize", api.ApiUserRequired(deauthorizeOAuthApp)).Methods("POST") - api.BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/regen_secret", api.ApiUserRequired(regenerateOAuthSecret)).Methods("POST") - api.BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/login", api.AppHandlerIndependent(loginWithOAuth)).Methods("GET") - api.BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/signup", api.AppHandlerIndependent(signupWithOAuth)).Methods("GET") -} - -func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) - return - } - - oauthApp := model.OAuthAppFromJson(r.Body) - - if oauthApp == nil { - c.SetInvalidParam("registerOAuthApp", "app") - return - } - - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - oauthApp.IsTrusted = false - } - - oauthApp.CreatorId = c.Session.UserId - - rapp, err := c.App.CreateOAuthApp(oauthApp) - - if err != nil { - c.Err = err - return - } - - c.LogAudit("client_id=" + rapp.Id) - w.Write([]byte(rapp.ToJson())) -} - -func getOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) - return - } - - var apps []*model.OAuthApp - var err *model.AppError - if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - apps, err = c.App.GetOAuthApps(0, 100000) - } else { - apps, err = c.App.GetOAuthAppsByCreator(c.Session.UserId, 0, 100000) - } - - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.OAuthAppListToJson(apps))) -} - -func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - clientId := params["client_id"] - - oauthApp, err := c.App.GetOAuthApp(clientId) - - if err != nil { - c.Err = err - return - } - - oauthApp.Sanitize() - w.Write([]byte(oauthApp.ToJson())) -} - -func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - responseType := r.URL.Query().Get("response_type") - if len(responseType) == 0 { - c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_response.app_error", nil, "", http.StatusBadRequest) - return - } - - clientId := r.URL.Query().Get("client_id") - if len(clientId) != 26 { - c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_client.app_error", nil, "", http.StatusBadRequest) - return - } - - redirectUri := r.URL.Query().Get("redirect_uri") - if len(redirectUri) == 0 { - c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_redirect.app_error", nil, "", http.StatusBadRequest) - return - } - - scope := r.URL.Query().Get("scope") - state := r.URL.Query().Get("state") - - c.LogAudit("attempt") - - authRequest := &model.AuthorizeRequest{ - ResponseType: responseType, - ClientId: clientId, - RedirectUri: redirectUri, - Scope: scope, - State: state, - } - - redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.Session.UserId, authRequest) - - if err != nil { - c.Err = err - return - } - - c.LogAudit("") - - w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl}))) -} - -func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) { - apps, err := c.App.GetAuthorizedAppsForUser(c.Session.UserId, 0, 10000) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.OAuthAppListToJson(apps))) -} - -func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - service := params["service"] - loginHint := r.URL.Query().Get("login_hint") - redirectTo := r.URL.Query().Get("redirect_to") - - teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) - if err != nil { - c.Err = err - return - } - - if authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint); err != nil { - c.Err = err - return - } else { - http.Redirect(w, r, authUrl, http.StatusFound) - } -} - -func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - service := params["service"] - - if !c.App.Config().TeamSettings.EnableUserCreation { - c.Err = model.NewAppError("signupWithOAuth", "api.oauth.singup_with_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) - if err != nil { - c.Err = err - return - } - - if authUrl, err := c.App.GetOAuthSignupEndpoint(w, r, service, teamId); err != nil { - c.Err = err - return - } else { - http.Redirect(w, r, authUrl, http.StatusFound) - } -} - -func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteOAuthApp", "id") - return - } - - c.LogAudit("attempt") - - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewAppError("deleteOAuthApp", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) - return - } - - oauthApp, err := c.App.GetOAuthApp(id) - if err != nil { - c.Err = err - return - } - - if c.Session.UserId != oauthApp.CreatorId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - c.LogAudit("fail - inappropriate permissions") - c.Err = model.NewAppError("deleteOAuthApp", "api.oauth.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusForbidden) - return - } - - err = c.App.DeleteOAuthApp(id) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - ReturnStatusOK(w) -} - -func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["id"] - - err := c.App.DeauthorizeOAuthAppForUser(c.Session.UserId, id) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - ReturnStatusOK(w) -} - -func regenerateOAuthSecret(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["id"] - - oauthApp, err := c.App.GetOAuthApp(id) - if err != nil { - c.Err = err - return - } - - if oauthApp.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - c.Err = model.NewAppError("regenerateOAuthSecret", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) - return - } - - oauthApp, err = c.App.RegenerateOAuthAppSecret(oauthApp) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(oauthApp.ToJson())) -} diff --git a/api/oauth_test.go b/api/oauth_test.go deleted file mode 100644 index 2428441b1..000000000 --- a/api/oauth_test.go +++ /dev/null @@ -1,895 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "encoding/base64" - "io" - "io/ioutil" - "net/http" - "net/url" - "strings" - "testing" - - "github.com/mattermost/mattermost-server/einterfaces" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" -) - -func TestOAuthRegisterApp(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - oauthApp := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true} - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if _, err := Client.RegisterApp(oauthApp); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - // calling the endpoint without an app - if _, err := Client.DoApiPost("/oauth/register", ""); err == nil { - t.Fatal("should have failed") - } - - Client.Logout() - - if _, err := Client.RegisterApp(oauthApp); err == nil { - t.Fatal("not logged in - should have failed") - } - - th.LoginSystemAdmin() - Client = th.SystemAdminClient - - if result, err := Client.RegisterApp(oauthApp); err != nil { - t.Fatal(err) - } else { - rapp := result.Data.(*model.OAuthApp) - if len(rapp.Id) != 26 { - t.Fatal("clientid didn't return properly") - } - if len(rapp.ClientSecret) != 26 { - t.Fatal("client secret didn't return properly") - } - } - - oauthApp = &model.OAuthApp{Name: "", Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - if _, err := Client.RegisterApp(oauthApp); err == nil { - t.Fatal("missing name - should have failed") - } - - oauthApp = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - if _, err := Client.RegisterApp(oauthApp); err == nil { - t.Fatal("missing homepage - should have failed") - } - - oauthApp = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{}} - if _, err := Client.RegisterApp(oauthApp); err == nil { - t.Fatal("missing callback url - should have failed") - } - - user := &model.User{Email: strings.ToLower("test+"+model.NewId()) + "@simulator.amazonses.com", Password: "hello1", Username: "n" + model.NewId(), EmailVerified: true} - - ruser := Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.App.UpdateUserRoles(ruser.Id, "", false) - - Client.Logout() - Client.Login(user.Email, user.Password) - - oauthApp = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true} - if _, err := Client.RegisterApp(oauthApp); err == nil { - t.Fatal("should have failed. not enough permissions") - } - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) - - th.LoginBasic() - - if result, err := th.BasicClient.RegisterApp(oauthApp); err != nil { - t.Fatal(err) - } else { - rapp := result.Data.(*model.OAuthApp) - if rapp.IsTrusted { - t.Fatal("trusted should be false - created by non admin") - } - } -} - -func TestOAuthAllow(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - oauthApp := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - oauthApp = AdminClient.Must(AdminClient.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - state := "123" - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", state); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - if result, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", state); err != nil { - t.Fatal(err) - } else { - redirect := result.Data.(map[string]string)["redirect"] - if len(redirect) == 0 { - t.Fatal("redirect url should be set") - } - - ru, _ := url.Parse(redirect) - if ru == nil { - t.Fatal("redirect url unparseable") - } else { - if len(ru.Query().Get("code")) == 0 { - t.Fatal("authorization code not returned") - } - if ru.Query().Get("state") != state { - t.Fatal("returned state doesn't match") - } - } - } - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, "", "all", state); err == nil { - t.Fatal("should have failed - no redirect_url given") - } - - if _, err := Client.AllowOAuth("", oauthApp.Id, "", "", state); err == nil { - t.Fatal("should have failed - no response type given") - } - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, "", "", state); err == nil { - t.Fatal("should have failed - no redirect_url given") - } - - if result, err := Client.AllowOAuth("junk", oauthApp.Id, oauthApp.CallbackUrls[0], "all", state); err != nil { - t.Fatal(err) - } else { - redirect := result.Data.(map[string]string)["redirect"] - if len(redirect) == 0 { - t.Fatal("redirect url should be set") - } - - ru, _ := url.Parse(redirect) - if ru == nil { - t.Fatal("redirect url unparseable") - } else { - if ru.Query().Get("error") != "unsupported_response_type" { - t.Fatal("wrong error returned") - } - if ru.Query().Get("state") != state { - t.Fatal("returned state doesn't match") - } - } - } - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "", oauthApp.CallbackUrls[0], "all", state); err == nil { - t.Fatal("should have failed - empty client id") - } - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "junk", oauthApp.CallbackUrls[0], "all", state); err == nil { - t.Fatal("should have failed - bad client id") - } - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, "https://somewhereelse.com", "", state); err == nil { - t.Fatal("should have failed - redirect uri host does not match app host") - } -} - -func TestOAuthGetAppsByUser(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if _, err := Client.GetOAuthAppsByUser(); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - if _, err := Client.GetOAuthAppsByUser(); err == nil { - t.Fatal("Should have failed.") - } - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) - - if result, err := Client.GetOAuthAppsByUser(); err != nil { - t.Fatal(err) - } else { - apps := result.Data.([]*model.OAuthApp) - - if len(apps) != 0 { - t.Fatal("incorrect number of apps should have been 0") - } - } - - oauthApp := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - oauthApp = Client.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if result, err := Client.GetOAuthAppsByUser(); err != nil { - t.Fatal(err) - } else { - apps := result.Data.([]*model.OAuthApp) - - if len(apps) != 1 { - t.Fatal("incorrect number of apps should have been 1") - } - } - - oauthApp = &model.OAuthApp{Name: "TestApp4" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - oauthApp = AdminClient.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if result, err := AdminClient.GetOAuthAppsByUser(); err != nil { - t.Fatal(err) - } else { - apps := result.Data.([]*model.OAuthApp) - - if len(apps) < 4 { - t.Fatal("incorrect number of apps should have been 4 or more") - } - } - - user := &model.User{Email: strings.ToLower("test+"+model.NewId()) + "@simulator.amazonses.com", Password: "hello1", Username: "n" + model.NewId(), EmailVerified: true} - ruser := Client.Must(AdminClient.CreateUser(user, "")).Data.(*model.User) - if _, err := th.App.UpdateUserRoles(ruser.Id, "", false); err != nil { - t.Fatal(err) - } - - Client.Logout() - Client.Login(user.Email, user.Password) - - if _, err := Client.GetOAuthAppsByUser(); err == nil { - t.Fatal("should have failed. not enough permissions") - } -} - -func TestOAuthGetAppInfo(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if _, err := Client.GetOAuthAppInfo("fakeId"); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - - oauthApp = AdminClient.Must(AdminClient.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := Client.GetOAuthAppInfo(model.NewId()); err == nil { - t.Fatal("Should have failed") - } - - if _, err := Client.GetOAuthAppInfo(oauthApp.Id); err != nil { - t.Fatal(err) - } -} - -func TestOAuthGetAuthorizedApps(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if _, err := Client.GetOAuthAuthorizedApps(); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - oauthApp = AdminClient.Must(AdminClient.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, "https://nowhere.com", "user", ""); err != nil { - t.Fatal(err) - } - - if result, err := Client.GetOAuthAuthorizedApps(); err != nil { - t.Fatal(err) - } else { - apps := result.Data.([]*model.OAuthApp) - - if len(apps) != 1 { - t.Fatal("incorrect number of apps should have been 1") - } - } -} - -func TestOAuthDeauthorizeApp(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if err := Client.OAuthDeauthorizeApp(model.NewId()); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - - oauthApp = AdminClient.Must(AdminClient.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, "https://nowhere.com", "user", ""); err != nil { - t.Fatal(err) - } - - if err := Client.OAuthDeauthorizeApp(""); err == nil { - t.Fatal("Should have failed - no id provided") - } - - a1 := model.AccessData{} - a1.ClientId = oauthApp.Id - a1.UserId = th.BasicUser.Id - a1.Token = model.NewId() - a1.RefreshToken = model.NewId() - a1.ExpiresAt = model.GetMillis() - a1.RedirectUri = "http://example.com" - <-th.App.Srv.Store.OAuth().SaveAccessData(&a1) - - if err := Client.OAuthDeauthorizeApp(oauthApp.Id); err != nil { - t.Fatal(err) - } - - if result, err := Client.GetOAuthAuthorizedApps(); err != nil { - t.Fatal(err) - } else { - apps := result.Data.([]*model.OAuthApp) - - if len(apps) != 0 { - t.Fatal("incorrect number of apps should have been 0") - } - } -} - -func TestOAuthRegenerateAppSecret(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if _, err := AdminClient.RegenerateOAuthAppSecret(model.NewId()); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - oauthApp := &model.OAuthApp{Name: "TestApp6" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - - oauthApp = AdminClient.Must(AdminClient.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := AdminClient.RegenerateOAuthAppSecret(model.NewId()); err == nil { - t.Fatal("Should have failed - invalid app id") - } - - if _, err := Client.RegenerateOAuthAppSecret(oauthApp.Id); err == nil { - t.Fatal("Should have failed - only admin or the user who registered the app are allowed to perform this action") - } - - if regenApp, err := AdminClient.RegenerateOAuthAppSecret(oauthApp.Id); err != nil { - t.Fatal(err) - } else { - app2 := regenApp.Data.(*model.OAuthApp) - if app2.Id != oauthApp.Id { - t.Fatal("Should have been the same app Id") - } - - if app2.ClientSecret == oauthApp.ClientSecret { - t.Fatal("Should have been different client Secrets") - } - } -} - -func TestOAuthDeleteApp(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - AdminClient := th.SystemAdminClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - if !th.App.Config().ServiceSettings.EnableOAuthServiceProvider { - if _, err := Client.DeleteOAuthApp("fakeId"); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) - - oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - - oauthApp = Client.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := Client.DeleteOAuthApp(oauthApp.Id); err != nil { - t.Fatal(err) - } - - oauthApp = &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - - oauthApp = Client.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := AdminClient.DeleteOAuthApp(""); err == nil { - t.Fatal("Should have failed - id not provided") - } - - if _, err := AdminClient.DeleteOAuthApp(model.NewId()); err == nil { - t.Fatal("Should have failed - invalid id") - } - - if _, err := AdminClient.DeleteOAuthApp(oauthApp.Id); err != nil { - t.Fatal(err) - } - - oauthApp = &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - oauthApp = AdminClient.Must(AdminClient.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - if _, err := Client.DeleteOAuthApp(oauthApp.Id); err == nil { - t.Fatal("Should have failed - only admin or the user who registered the app are allowed to perform this action") - } - - user := &model.User{Email: strings.ToLower("test+"+model.NewId()) + "@simulator.amazonses.com", Password: "hello1", Username: "n" + model.NewId(), EmailVerified: true} - ruser := Client.Must(AdminClient.CreateUser(user, "")).Data.(*model.User) - th.App.UpdateUserRoles(ruser.Id, "", false) - - Client.Logout() - Client.Login(user.Email, user.Password) - if _, err := Client.DeleteOAuthApp(oauthApp.Id); err == nil { - t.Fatal("Should have failed - not enough permissions") - } -} - -func TestOAuthAccessToken(t *testing.T) { - if testing.Short() { - t.SkipNow() - } - - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) - - oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} - oauthApp = Client.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) - data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}} - - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - oauth providing turned off") - } - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - redirect := Client.Must(Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"] - rurl, _ := url.Parse(redirect) - - Client.Logout() - - data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}} - - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - bad grant type") - } - - data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) - data.Set("client_id", "") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - missing client id") - } - data.Set("client_id", "junk") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - bad client id") - } - - data.Set("client_id", oauthApp.Id) - data.Set("client_secret", "") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - missing client secret") - } - - data.Set("client_secret", "junk") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - bad client secret") - } - - data.Set("client_secret", oauthApp.ClientSecret) - data.Set("code", "") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - missing code") - } - - data.Set("code", "junk") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - bad code") - } - - data.Set("code", rurl.Query().Get("code")) - data.Set("redirect_uri", "junk") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - non-matching redirect uri") - } - - // reset data for successful request - data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) - data.Set("client_id", oauthApp.Id) - data.Set("client_secret", oauthApp.ClientSecret) - data.Set("code", rurl.Query().Get("code")) - data.Set("redirect_uri", oauthApp.CallbackUrls[0]) - - token := "" - refreshToken := "" - if result, err := Client.GetAccessToken(data); err != nil { - t.Fatal(err) - } else { - rsp := result.Data.(*model.AccessResponse) - if len(rsp.AccessToken) == 0 { - t.Fatal("access token not returned") - } else if len(rsp.RefreshToken) == 0 { - t.Fatal("refresh token not returned") - } else { - token = rsp.AccessToken - refreshToken = rsp.RefreshToken - } - if rsp.TokenType != model.ACCESS_TOKEN_TYPE { - t.Fatal("access token type incorrect") - } - } - - if result, err := Client.DoApiGet("/teams/"+th.BasicTeam.Id+"/users/0/100?access_token="+token, "", ""); err != nil { - t.Fatal(err) - } else { - userMap := model.UserMapFromJson(result.Body) - if len(userMap) == 0 { - t.Fatal("user map empty - did not get results correctly") - } - } - - if _, err := Client.DoApiGet("/teams/"+th.BasicTeam.Id+"/users/0/100", "", ""); err == nil { - t.Fatal("should have failed - no access token provided") - } - - if _, err := Client.DoApiGet("/teams/"+th.BasicTeam.Id+"/users/0/100?access_token=junk", "", ""); err == nil { - t.Fatal("should have failed - bad access token provided") - } - - Client.SetOAuthToken(token) - if result, err := Client.DoApiGet("/teams/"+th.BasicTeam.Id+"/users/0/100", "", ""); err != nil { - t.Fatal(err) - } else { - userMap := model.UserMapFromJson(result.Body) - if len(userMap) == 0 { - t.Fatal("user map empty - did not get results correctly") - } - } - - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("should have failed - tried to reuse auth code") - } - - data.Set("grant_type", model.REFRESH_TOKEN_GRANT_TYPE) - data.Set("client_id", oauthApp.Id) - data.Set("client_secret", oauthApp.ClientSecret) - data.Set("refresh_token", "") - data.Set("redirect_uri", oauthApp.CallbackUrls[0]) - data.Del("code") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("Should have failed - refresh token empty") - } - - data.Set("refresh_token", refreshToken) - if result, err := Client.GetAccessToken(data); err != nil { - t.Fatal(err) - } else { - rsp := result.Data.(*model.AccessResponse) - if len(rsp.AccessToken) == 0 { - t.Fatal("access token not returned") - } else if len(rsp.RefreshToken) == 0 { - t.Fatal("refresh token not returned") - } else if rsp.RefreshToken == refreshToken { - t.Fatal("refresh token did not update") - } - - if rsp.TokenType != model.ACCESS_TOKEN_TYPE { - t.Fatal("access token type incorrect") - } - Client.SetOAuthToken(rsp.AccessToken) - _, err = Client.GetMe("") - if err != nil { - t.Fatal(err) - } - - data.Set("refresh_token", rsp.RefreshToken) - } - - if result, err := Client.GetAccessToken(data); err != nil { - t.Fatal(err) - } else { - rsp := result.Data.(*model.AccessResponse) - if len(rsp.AccessToken) == 0 { - t.Fatal("access token not returned") - } else if len(rsp.RefreshToken) == 0 { - t.Fatal("refresh token not returned") - } else if rsp.RefreshToken == refreshToken { - t.Fatal("refresh token did not update") - } - - if rsp.TokenType != model.ACCESS_TOKEN_TYPE { - t.Fatal("access token type incorrect") - } - Client.SetOAuthToken(rsp.AccessToken) - _, err = Client.GetMe("") - if err != nil { - t.Fatal(err) - } - } - - authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1} - <-th.App.Srv.Store.OAuth().SaveAuthData(authData) - - data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) - data.Set("client_id", oauthApp.Id) - data.Set("client_secret", oauthApp.ClientSecret) - data.Set("redirect_uri", oauthApp.CallbackUrls[0]) - data.Set("code", authData.Code) - data.Del("refresh_token") - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("Should have failed - code is expired") - } - - Client.ClearOAuthToken() -} - -func TestOAuthComplete(t *testing.T) { - if testing.Short() { - t.SkipNow() - } - - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if r, err := HttpGet(Client.Url+"/login/gitlab/complete", Client.HttpClient, "", true); err == nil { - t.Fatal("should have failed - no code provided") - closeBody(r) - } - - if r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123", Client.HttpClient, "", true); err == nil { - t.Fatal("should have failed - gitlab disabled") - closeBody(r) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = true }) - if r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", Client.HttpClient, "", true); err == nil { - t.Fatal("should have failed - gitlab disabled") - closeBody(r) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = model.NewId() }) - - stateProps := map[string]string{} - stateProps["action"] = model.OAUTH_ACTION_LOGIN - stateProps["team_id"] = th.BasicTeam.Id - stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint - - state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) - if r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true); err == nil { - t.Fatal("should have failed - bad state") - closeBody(r) - } - - stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id) - state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) - if r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true); err == nil { - t.Fatal("should have failed - no connection") - closeBody(r) - } - - // We are going to use mattermost as the provider emulating gitlab - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) - - oauthApp := &model.OAuthApp{ - Name: "TestApp5" + model.NewId(), - Homepage: "https://nowhere.com", - Description: "test", - CallbackUrls: []string{ - Client.Url + "/signup/" + model.SERVICE_GITLAB + "/complete", - Client.Url + "/login/" + model.SERVICE_GITLAB + "/complete", - }, - IsTrusted: true, - } - oauthApp = Client.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = oauthApp.Id }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = oauthApp.ClientSecret }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = Client.Url + "/oauth/access_token" }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserApiEndpoint = Client.ApiUrl + "/users/me" }) - - provider := &MattermostTestProvider{} - - redirect := Client.Must(Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"] - rurl, _ := url.Parse(redirect) - code := rurl.Query().Get("code") - stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO - delete(stateProps, "team_id") - stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint - stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id) - stateProps["redirect_to"] = "/oauth/authorize" - state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) - if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { - closeBody(r) - } - - einterfaces.RegisterOauthProvider(model.SERVICE_GITLAB, provider) - redirect = Client.Must(Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"] - rurl, _ = url.Parse(redirect) - code = rurl.Query().Get("code") - if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { - closeBody(r) - } - - if result := <-th.App.Srv.Store.User().UpdateAuthData( - th.BasicUser.Id, model.SERVICE_GITLAB, &th.BasicUser.Email, th.BasicUser.Email, true); result.Err != nil { - t.Fatal(result.Err) - } - - redirect = Client.Must(Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"] - rurl, _ = url.Parse(redirect) - code = rurl.Query().Get("code") - stateProps["action"] = model.OAUTH_ACTION_LOGIN - state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) - if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { - closeBody(r) - } - - redirect = Client.Must(Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"] - rurl, _ = url.Parse(redirect) - code = rurl.Query().Get("code") - delete(stateProps, "action") - state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) - if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { - closeBody(r) - } - - redirect = Client.Must(Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, oauthApp.Id, oauthApp.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"] - rurl, _ = url.Parse(redirect) - code = rurl.Query().Get("code") - stateProps["action"] = model.OAUTH_ACTION_SIGNUP - state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) - if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { - closeBody(r) - } -} - -func HttpGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, *model.AppError) { - rq, _ := http.NewRequest("GET", url, nil) - rq.Close = true - - if len(authToken) > 0 { - rq.Header.Set(model.HEADER_AUTH, authToken) - } - - if !followRedirect { - httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - } - } - - if rp, err := httpClient.Do(rq); err != nil { - return nil, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode == 304 { - return rp, nil - } else if rp.StatusCode == 307 { - return rp, nil - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return rp, model.AppErrorFromJson(rp.Body) - } else { - return rp, nil - } -} - -func closeBody(r *http.Response) { - if r.Body != nil { - ioutil.ReadAll(r.Body) - r.Body.Close() - } -} - -type MattermostTestProvider struct { -} - -func (m *MattermostTestProvider) GetIdentifier() string { - return model.SERVICE_GITLAB -} - -func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User { - return model.UserFromJson(data) -} - -func (m *MattermostTestProvider) GetAuthDataFromJson(data io.Reader) string { - authData := model.UserFromJson(data) - return authData.Email -} diff --git a/api/post.go b/api/post.go deleted file mode 100644 index bed2f3fdb..000000000 --- a/api/post.go +++ /dev/null @@ -1,560 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - "strconv" - "time" - - "github.com/gorilla/mux" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" -) - -const OPEN_GRAPH_METADATA_CACHE_SIZE = 10000 - -var openGraphDataCache = utils.NewLru(OPEN_GRAPH_METADATA_CACHE_SIZE) - -func (api *API) InitPost() { - api.BaseRoutes.ApiRoot.Handle("/get_opengraph_metadata", api.ApiUserRequired(getOpenGraphMetadata)).Methods("POST") - - api.BaseRoutes.NeedTeam.Handle("/posts/search", api.ApiUserRequiredActivity(searchPosts, true)).Methods("POST") - api.BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getFlaggedPosts)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/posts/{post_id}", api.ApiUserRequired(getPostById)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/pltmp/{post_id}", api.ApiUserRequired(getPermalinkTmp)).Methods("GET") - - api.BaseRoutes.Posts.Handle("/create", api.ApiUserRequiredActivity(createPost, true)).Methods("POST") - api.BaseRoutes.Posts.Handle("/update", api.ApiUserRequiredActivity(updatePost, true)).Methods("POST") - api.BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getPosts)).Methods("GET") - api.BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", api.ApiUserRequired(getPostsSince)).Methods("GET") - - api.BaseRoutes.NeedPost.Handle("/get", api.ApiUserRequired(getPost)).Methods("GET") - api.BaseRoutes.NeedPost.Handle("/delete", api.ApiUserRequiredActivity(deletePost, true)).Methods("POST") - api.BaseRoutes.NeedPost.Handle("/before/{offset:[0-9]+}/{num_posts:[0-9]+}", api.ApiUserRequired(getPostsBefore)).Methods("GET") - api.BaseRoutes.NeedPost.Handle("/after/{offset:[0-9]+}/{num_posts:[0-9]+}", api.ApiUserRequired(getPostsAfter)).Methods("GET") - api.BaseRoutes.NeedPost.Handle("/get_file_infos", api.ApiUserRequired(getFileInfosForPost)).Methods("GET") - api.BaseRoutes.NeedPost.Handle("/pin", api.ApiUserRequired(pinPost)).Methods("POST") - api.BaseRoutes.NeedPost.Handle("/unpin", api.ApiUserRequired(unpinPost)).Methods("POST") -} - -func createPost(c *Context, w http.ResponseWriter, r *http.Request) { - post := model.PostFromJson(r.Body) - if post == nil { - c.SetInvalidParam("createPost", "post") - return - } - - post.UserId = c.Session.UserId - - hasPermission := false - if c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_CREATE_POST) { - hasPermission = true - } else if channel, err := c.App.GetChannel(post.ChannelId); err == nil { - // Temporary permission check method until advanced permissions, please do not copy - if channel.Type == model.CHANNEL_OPEN && c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_POST_PUBLIC) { - hasPermission = true - } - } - - if !hasPermission { - c.SetPermissionError(model.PERMISSION_CREATE_POST) - return - } - - if post.CreateAt != 0 && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - post.CreateAt = 0 - } - - rp, err := c.App.CreatePostAsUser(post) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(rp.ToJson())) -} - -func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { - post := model.PostFromJson(r.Body) - - if post == nil { - c.SetInvalidParam("updatePost", "post") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_EDIT_POST) { - c.SetPermissionError(model.PERMISSION_EDIT_POST) - return - } - - post.UserId = c.Session.UserId - - rpost, err := c.App.UpdatePost(post, true) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(rpost.ToJson())) -} - -func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinned bool) { - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("savedIsPinnedPost", "channelId") - return - } - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("savedIsPinnedPost", "postId") - return - } - - pchan := c.App.Srv.Store.Post().Get(postId) - - var oldPost *model.Post - if result := <-pchan; result.Err != nil { - c.Err = result.Err - return - } else { - oldPost = result.Data.(*model.PostList).Posts[postId] - newPost := &model.Post{} - *newPost = *oldPost - newPost.IsPinned = isPinned - - if result := <-c.App.Srv.Store.Post().Update(newPost, oldPost); result.Err != nil { - c.Err = result.Err - return - } else { - rpost := result.Data.(*model.Post) - - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil) - message.Add("post", c.App.PostWithProxyAddedToImageURLs(rpost).ToJson()) - c.App.Publish(message) - - c.App.InvalidateCacheForChannelPosts(rpost.ChannelId) - - w.Write([]byte(rpost.ToJson())) - } - } -} - -func pinPost(c *Context, w http.ResponseWriter, r *http.Request) { - saveIsPinnedPost(c, w, r, true) -} - -func unpinPost(c *Context, w http.ResponseWriter, r *http.Request) { - saveIsPinnedPost(c, w, r, false) -} - -func getFlaggedPosts(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getFlaggedPosts", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getFlaggedPosts", "limit") - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_VIEW_TEAM) { - c.SetPermissionError(model.PERMISSION_VIEW_TEAM) - return - } - - if posts, err := c.App.GetFlaggedPostsForTeam(c.Session.UserId, c.TeamId, offset, limit); err != nil { - c.Err = err - return - } else { - w.Write([]byte(posts.ToJson())) - } -} - -func getPosts(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - id := params["channel_id"] - if len(id) != 26 { - c.SetInvalidParam("getPosts", "channelId") - return - } - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getPosts", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getPosts", "limit") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_CREATE_POST) { - c.SetPermissionError(model.PERMISSION_CREATE_POST) - return - } - - etag := c.App.GetPostsEtag(id) - - if c.HandleEtag(etag, "Get Posts", w, r) { - return - } - - if list, err := c.App.GetPosts(id, offset, limit); err != nil { - c.Err = err - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Write([]byte(list.ToJson())) - } - -} - -func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - id := params["channel_id"] - if len(id) != 26 { - c.SetInvalidParam("getPostsSince", "channelId") - return - } - - time, err := strconv.ParseInt(params["time"], 10, 64) - if err != nil { - c.SetInvalidParam("getPostsSince", "time") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if list, err := c.App.GetPostsSince(id, time); err != nil { - c.Err = err - return - } else { - w.Write([]byte(list.ToJson())) - } - -} - -func getPost(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("getPost", "channelId") - return - } - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("getPost", "postId") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if list, err := c.App.GetPostThread(postId); err != nil { - c.Err = err - return - } else if c.HandleEtag(list.Etag(), "Get Post", w, r) { - return - } else { - if !list.IsChannelId(channelId) { - c.Err = model.NewAppError("getPost", "api.post.get_post.permissions.app_error", nil, "", http.StatusForbidden) - return - } - - w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag()) - w.Write([]byte(list.ToJson())) - } -} - -func getPostById(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("getPostById", "postId") - return - } - - if list, err := c.App.GetPostThread(postId); err != nil { - c.Err = err - return - } else { - if len(list.Order) != 1 { - c.Err = model.NewAppError("getPostById", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusInternalServerError) - return - } - post := list.Posts[list.Order[0]] - - if !c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if c.HandleEtag(list.Etag(), "Get Post By Id", w, r) { - return - } - - w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag()) - w.Write([]byte(list.ToJson())) - } -} - -func getPermalinkTmp(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("getPermalinkTmp", "postId") - return - } - - var channel *model.Channel - if result := <-c.App.Srv.Store.Channel().GetForPost(postId); result.Err == nil { - channel = result.Data.(*model.Channel) - } else { - c.SetInvalidParam("getPermalinkTmp", "postId") - return - } - - if channel.Type == model.CHANNEL_OPEN { - if !c.App.HasPermissionToChannelByPost(c.Session.UserId, postId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) { - c.SetPermissionError(model.PERMISSION_JOIN_PUBLIC_CHANNELS) - return - } - } else { - if !c.App.HasPermissionToChannelByPost(c.Session.UserId, postId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - } - - if list, err := c.App.GetPermalinkPost(postId, c.Session.UserId); err != nil { - c.Err = err - return - } else if c.HandleEtag(list.Etag(), "Get Permalink TMP", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag()) - w.Write([]byte(list.ToJson())) - } -} - -func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("deletePost", "channelId") - return - } - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("deletePost", "postId") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_DELETE_POST) { - c.SetPermissionError(model.PERMISSION_DELETE_POST) - return - } - - if !c.App.SessionHasPermissionToPost(c.Session, postId, model.PERMISSION_DELETE_OTHERS_POSTS) { - c.SetPermissionError(model.PERMISSION_DELETE_OTHERS_POSTS) - return - } - - if post, err := c.App.DeletePost(postId); err != nil { - c.Err = err - return - } else { - if post.ChannelId != channelId { - c.Err = model.NewAppError("deletePost", "api.post.delete_post.permissions.app_error", nil, "", http.StatusForbidden) - return - } - - result := make(map[string]string) - result["id"] = postId - w.Write([]byte(model.MapToJson(result))) - } -} - -func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) { - getPostsBeforeOrAfter(c, w, r, true) -} - -func getPostsAfter(c *Context, w http.ResponseWriter, r *http.Request) { - getPostsBeforeOrAfter(c, w, r, false) -} - -func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, before bool) { - params := mux.Vars(r) - - id := params["channel_id"] - if len(id) != 26 { - c.SetInvalidParam("getPostsBeforeOrAfter", "channelId") - return - } - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("getPostsBeforeOrAfter", "postId") - return - } - - numPosts, err := strconv.Atoi(params["num_posts"]) - if err != nil || numPosts <= 0 { - c.SetInvalidParam("getPostsBeforeOrAfter", "numPosts") - return - } - - offset, err := strconv.Atoi(params["offset"]) - if err != nil || offset < 0 { - c.SetInvalidParam("getPostsBeforeOrAfter", "offset") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - // We can do better than this etag in this situation - etag := c.App.GetPostsEtag(id) - - if c.HandleEtag(etag, "Get Posts Before or After", w, r) { - return - } - - if list, err := c.App.GetPostsAroundPost(postId, id, offset, numPosts, before); err != nil { - c.Err = err - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Write([]byte(list.ToJson())) - } -} - -func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.StringInterfaceFromJson(r.Body) - - terms := props["terms"].(string) - if len(terms) == 0 { - c.SetInvalidParam("search", "terms") - return - } - - isOrSearch := false - if val, ok := props["is_or_search"]; ok && val != nil { - isOrSearch = val.(bool) - } - - startTime := time.Now() - - posts, err := c.App.SearchPostsInTeam(terms, c.Session.UserId, c.TeamId, isOrSearch) - - elapsedTime := float64(time.Since(startTime)) / float64(time.Second) - metrics := c.App.Metrics - if metrics != nil { - metrics.IncrementPostsSearchCounter() - metrics.ObservePostsSearchDuration(elapsedTime) - } - - if err != nil { - c.Err = err - return - } - - w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Write([]byte(posts.ToJson())) -} - -func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("getFileInfosForPost", "channelId") - return - } - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("getFileInfosForPost", "postId") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if infos, err := c.App.GetFileInfosForPost(postId, false); err != nil { - c.Err = err - return - } else if c.HandleEtag(model.GetEtagForFileInfos(infos), "Get File Infos For Post", w, r) { - return - } else { - if len(infos) > 0 { - w.Header().Set("Cache-Control", "max-age=2592000, public") - } - - w.Header().Set(model.HEADER_ETAG_SERVER, model.GetEtagForFileInfos(infos)) - w.Write([]byte(model.FileInfosToJson(infos))) - } -} - -func getOpenGraphMetadata(c *Context, w http.ResponseWriter, r *http.Request) { - if !*c.App.Config().ServiceSettings.EnableLinkPreviews { - c.Err = model.NewAppError("getOpenGraphMetadata", "api.post.link_preview_disabled.app_error", nil, "", http.StatusNotImplemented) - return - } - - props := model.StringInterfaceFromJson(r.Body) - - ogJSONGeneric, ok := openGraphDataCache.Get(props["url"]) - if ok { - w.Write(ogJSONGeneric.([]byte)) - return - } - - url := "" - ok = false - if url, ok = props["url"].(string); len(url) == 0 || !ok { - c.SetInvalidParam("getOpenGraphMetadata", "url") - return - } - - og := c.App.GetOpenGraphMetadata(url) - - ogJSON, err := og.ToJSON() - openGraphDataCache.AddWithExpiresInSecs(props["url"], ogJSON, 3600) // Cache would expire after 1 hour - if err != nil { - w.Write([]byte(`{"url": ""}`)) - return - } - - w.Write(ogJSON) -} diff --git a/api/post_test.go b/api/post_test.go deleted file mode 100644 index 7a2367312..000000000 --- a/api/post_test.go +++ /dev/null @@ -1,1510 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "reflect" - "strings" - - "testing" - "time" - - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" -) - -func TestCreatePost(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - team := th.BasicTeam - team2 := th.CreateTeam(th.BasicClient) - user3 := th.CreateUser(th.BasicClient) - th.LinkUserToTeam(user3, team2) - channel1 := th.BasicChannel - channel2 := th.CreateChannel(Client, team) - - th.InitSystemAdmin() - AdminClient := th.SystemAdminClient - adminTeam := th.SystemAdminTeam - adminUser := th.CreateUser(th.SystemAdminClient) - th.LinkUserToTeam(adminUser, adminTeam) - - post1 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag a" + model.NewId() + "a", Props: model.StringInterface{model.PROPS_ADD_CHANNEL_MEMBER: "no good"}} - rpost1, err := Client.CreatePost(post1) - if err != nil { - t.Fatal(err) - } - - if rpost1.Data.(*model.Post).Message != post1.Message { - t.Fatal("message didn't match") - } - - if rpost1.Data.(*model.Post).Hashtags != "#hashtag" { - t.Fatal("hashtag didn't match") - } - - if len(rpost1.Data.(*model.Post).FileIds) != 0 { - t.Fatal("shouldn't have files") - } - - if rpost1.Data.(*model.Post).EditAt != 0 { - t.Fatal("Newly craeted post shouldn't have EditAt set") - } - - if rpost1.Data.(*model.Post).Props[model.PROPS_ADD_CHANNEL_MEMBER] != nil { - t.Fatal("newly created post shouldn't have Props['add_channel_member'] set") - } - - _, err = Client.CreatePost(&model.Post{ChannelId: channel1.Id, Message: "#hashtag a" + model.NewId() + "a", Type: model.POST_SYSTEM_GENERIC}) - if err == nil { - t.Fatal("should have failed - bad post type") - } - - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} - rpost2, err := Client.CreatePost(post2) - if err != nil { - t.Fatal(err) - } - - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id, ParentId: rpost2.Data.(*model.Post).Id} - _, err = Client.CreatePost(post3) - if err != nil { - t.Fatal(err) - } - - post4 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: "junk"} - _, err = Client.CreatePost(post4) - if err.StatusCode != http.StatusBadRequest { - t.Fatal("Should have been invalid param") - } - - post5 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id, ParentId: "junk"} - _, err = Client.CreatePost(post5) - if err.StatusCode != http.StatusBadRequest { - t.Fatal("Should have been invalid param") - } - - post1c2 := &model.Post{ChannelId: channel2.Id, Message: "zz" + model.NewId() + "a"} - rpost1c2, err := Client.CreatePost(post1c2) - - post2c2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1c2.Data.(*model.Post).Id} - _, err = Client.CreatePost(post2c2) - if err.StatusCode != http.StatusBadRequest { - t.Fatal("Should have been invalid param") - } - - post6 := &model.Post{ChannelId: "junk", Message: "zz" + model.NewId() + "a"} - _, err = Client.CreatePost(post6) - if err.StatusCode != http.StatusForbidden { - t.Fatal("Should have been forbidden") - } - - th.LoginBasic2() - - post7 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - _, err = Client.CreatePost(post7) - if err.StatusCode != http.StatusForbidden { - t.Fatal("Should have been forbidden") - } - - Client.Login(user3.Email, user3.Password) - Client.SetTeamId(team2.Id) - channel3 := th.CreateChannel(Client, team2) - - post8 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - _, err = Client.CreatePost(post8) - if err.StatusCode != http.StatusForbidden { - t.Fatal("Should have been forbidden") - } - - if _, err = Client.DoApiPost("/channels/"+channel3.Id+"/create", "garbage"); err == nil { - t.Fatal("should have been an error") - } - - fileIds := make([]string, 4) - if data, err := readTestFile("test.png"); err != nil { - t.Fatal(err) - } else { - for i := 0; i < 3; i++ { - fileIds[i] = Client.MustGeneric(Client.UploadPostAttachment(data, channel3.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - } - - // Make sure duplicated file ids are removed - fileIds[3] = fileIds[0] - - post9 := &model.Post{ - ChannelId: channel3.Id, - Message: "test", - FileIds: fileIds, - } - if resp, err := Client.CreatePost(post9); err != nil { - t.Fatal(err) - } else if rpost9 := resp.Data.(*model.Post); len(rpost9.FileIds) != 3 { - t.Fatal("post should have 3 files") - } else { - infos := store.Must(th.App.Srv.Store.FileInfo().GetForPost(rpost9.Id, true, true)).([]*model.FileInfo) - - if len(infos) != 3 { - t.Fatal("should've attached all 3 files to post") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetLicense(model.NewTestLicense()) - - defaultChannel := store.Must(th.App.Srv.Store.Channel().GetByName(team.Id, model.DEFAULT_CHANNEL, true)).(*model.Channel) - defaultPost := &model.Post{ - ChannelId: defaultChannel.Id, - Message: "Default Channel Post", - } - if _, err = Client.CreatePost(defaultPost); err == nil { - t.Fatal("should have failed -- ExperimentalTownSquareIsReadOnly is true and it's a read only channel") - } - - adminDefaultChannel := store.Must(th.App.Srv.Store.Channel().GetByName(adminTeam.Id, model.DEFAULT_CHANNEL, true)).(*model.Channel) - adminDefaultPost := &model.Post{ - ChannelId: adminDefaultChannel.Id, - Message: "Admin Default Channel Post", - } - if _, err = AdminClient.CreatePost(adminDefaultPost); err != nil { - t.Fatal("should not have failed -- ExperimentalTownSquareIsReadOnly is true and admin can post to channel") - } -} - -func TestCreatePostWithCreateAt(t *testing.T) { - - // An ordinary user cannot use CreateAt - - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - post := &model.Post{ - ChannelId: channel1.Id, - Message: "PLT-4349", - CreateAt: 1234, - } - if resp, err := Client.CreatePost(post); err != nil { - t.Fatal(err) - } else if rpost := resp.Data.(*model.Post); rpost.CreateAt == post.CreateAt { - t.Fatal("post should be created with default CreateAt timestamp for ordinary user") - } - - // But a System Admin user can - - th.InitSystemAdmin() - SysClient := th.SystemAdminClient - - if resp, err := SysClient.CreatePost(post); err != nil { - t.Fatal(err) - } else if rpost := resp.Data.(*model.Post); rpost.CreateAt != post.CreateAt { - t.Fatal("post should be created with provided CreateAt timestamp for System Admin user") - } -} - -func testCreatePostWithOutgoingHook( - t *testing.T, - hookContentType, expectedContentType, message, triggerWord string, - fileIds []string, - triggerWhen int, -) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - user := th.SystemAdminUser - channel := th.CreateChannel(Client, team) - - th.App.UpdateConfig(func(cfg *model.Config) { - cfg.ServiceSettings.EnableOutgoingWebhooks = true - *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" - }) - - var hook *model.OutgoingWebhook - var post *model.Post - - // Create a test server that is the target of the outgoing webhook. It will - // validate the webhook body fields and write to the success channel on - // success/failure. - success := make(chan bool) - wait := make(chan bool, 1) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-wait - - requestContentType := r.Header.Get("Content-Type") - if requestContentType != expectedContentType { - t.Logf("Content-Type is %s, should be %s", requestContentType, expectedContentType) - success <- false - return - } - - expectedPayload := &model.OutgoingWebhookPayload{ - Token: hook.Token, - TeamId: hook.TeamId, - TeamDomain: team.Name, - ChannelId: post.ChannelId, - ChannelName: channel.Name, - Timestamp: post.CreateAt, - UserId: post.UserId, - UserName: user.Username, - PostId: post.Id, - Text: post.Message, - TriggerWord: triggerWord, - FileIds: strings.Join(post.FileIds, ","), - } - - // depending on the Content-Type, we expect to find a JSON or form encoded payload - if requestContentType == "application/json" { - decoder := json.NewDecoder(r.Body) - o := &model.OutgoingWebhookPayload{} - decoder.Decode(&o) - - if !reflect.DeepEqual(expectedPayload, o) { - t.Logf("JSON payload is %+v, should be %+v", o, expectedPayload) - success <- false - return - } - } else { - err := r.ParseForm() - if err != nil { - t.Logf("Error parsing form: %q", err) - success <- false - return - } - - expectedFormValues, _ := url.ParseQuery(expectedPayload.ToFormValues()) - if !reflect.DeepEqual(expectedFormValues, r.Form) { - t.Logf("Form values are %q, should be %q", r.Form, expectedFormValues) - success <- false - return - } - } - - resp := &model.OutgoingWebhookResponse{} - resp.Text = model.NewString("some test text") - resp.Username = "testusername" - resp.IconURL = "http://www.mattermost.org/wp-content/uploads/2016/04/icon.png" - resp.Props = map[string]interface{}{"someprop": "somevalue"} - resp.Type = "custom_test" - - w.Write([]byte(resp.ToJson())) - - success <- true - })) - defer ts.Close() - - // create an outgoing webhook, passing it the test server URL - var triggerWords []string - if triggerWord != "" { - triggerWords = []string{triggerWord} - } - - hook = &model.OutgoingWebhook{ - ChannelId: channel.Id, - TeamId: team.Id, - ContentType: hookContentType, - TriggerWords: triggerWords, - TriggerWhen: triggerWhen, - CallbackURLs: []string{ts.URL}, - } - - if result, err := Client.CreateOutgoingWebhook(hook); err != nil { - t.Fatal(err) - } else { - hook = result.Data.(*model.OutgoingWebhook) - } - - // create a post to trigger the webhook - post = &model.Post{ - ChannelId: channel.Id, - Message: message, - FileIds: fileIds, - } - - if result, err := Client.CreatePost(post); err != nil { - t.Fatal(err) - } else { - post = result.Data.(*model.Post) - } - - wait <- true - - // We wait for the test server to write to the success channel and we make - // the test fail if that doesn't happen before the timeout. - select { - case ok := <-success: - if !ok { - t.Fatal("Test server did send an invalid webhook.") - } - case <-time.After(time.Second): - t.Fatal("Timeout, test server did not send the webhook.") - } -} - -func TestCreatePostWithOutgoingHook_form_urlencoded(t *testing.T) { - testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH) - testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH) - testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH) - testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH) -} - -func TestCreatePostWithOutgoingHook_json(t *testing.T) { - testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH) - testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH) - testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH) - testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH) -} - -// hooks created before we added the ContentType field should be considered as -// application/x-www-form-urlencoded -func TestCreatePostWithOutgoingHook_no_content_type(t *testing.T) { - testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_EXACT_MATCH) - testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TRIGGERWORDS_STARTS_WITH) - testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_EXACT_MATCH) - testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TRIGGERWORDS_STARTS_WITH) -} - -func TestUpdatePost(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.App.SetLicense(model.NewTestLicense()) - - th.AddPermissionToRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID) - - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - rpost1, err := Client.CreatePost(post1) - if err != nil { - t.Fatal(err) - } - - if rpost1.Data.(*model.Post).Message != post1.Message { - t.Fatal("full name didn't match") - } - - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} - rpost2, err := Client.CreatePost(post2) - if err != nil { - t.Fatal(err) - } - - if rpost2.Data.(*model.Post).EditAt != 0 { - t.Fatal("Newly craeted post shouldn't have EditAt set") - } - - msg2 := "zz" + model.NewId() + " update post 1" - rpost2.Data.(*model.Post).Message = msg2 - rpost2.Data.(*model.Post).Props[model.PROPS_ADD_CHANNEL_MEMBER] = "no good" - if rupost2, err := Client.UpdatePost(rpost2.Data.(*model.Post)); err != nil { - t.Fatal(err) - } else { - if rupost2.Data.(*model.Post).Message != msg2 { - t.Fatal("failed to updates") - } - if rupost2.Data.(*model.Post).EditAt == 0 { - t.Fatal("EditAt not updated for post") - } - if rupost2.Data.(*model.Post).Props[model.PROPS_ADD_CHANNEL_MEMBER] != nil { - t.Fatal("failed to sanitize Props['add_channel_member'], should be nil") - } - } - - msg1 := "#hashtag a" + model.NewId() + " update post 2" - rpost1.Data.(*model.Post).Message = msg1 - if rupost1, err := Client.UpdatePost(rpost1.Data.(*model.Post)); err != nil { - t.Fatal(err) - } else { - if rupost1.Data.(*model.Post).Message != msg1 && rupost1.Data.(*model.Post).Hashtags != "#hashtag" { - t.Fatal("failed to updates") - } - } - - up12 := &model.Post{Id: rpost1.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: "zz" + model.NewId() + " updaet post 1 update 2"} - if rup12, err := Client.UpdatePost(up12); err != nil { - t.Fatal(err) - } else { - if rup12.Data.(*model.Post).Message != up12.Message { - t.Fatal("failed to updates") - } - } - - rpost3, err := th.App.CreatePost(&model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", Type: model.POST_JOIN_LEAVE, UserId: th.BasicUser.Id}, channel1, false) - if err != nil { - t.Fatal(err) - } - - up3 := &model.Post{Id: rpost3.Id, ChannelId: channel1.Id, Message: "zz" + model.NewId() + " update post 3"} - if _, err := Client.UpdatePost(up3); err == nil { - t.Fatal("shouldn't have been able to update system message") - } - - // Test licensed policy controls for edit post - th.RemovePermissionFromRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID) - - post4 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} - rpost4, err := Client.CreatePost(post4) - if err != nil { - t.Fatal(err) - } - - up4 := &model.Post{Id: rpost4.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: "zz" + model.NewId() + " update post 4"} - if _, err := Client.UpdatePost(up4); err == nil { - t.Fatal("shouldn't have been able to update a message when not allowed") - } - - th.AddPermissionToRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.PostEditTimeLimit = 1 }) //seconds - - post5 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} - rpost5, err := Client.CreatePost(post5) - if err != nil { - t.Fatal(err) - } - - msg5 := "zz" + model.NewId() + " update post 5" - up5 := &model.Post{Id: rpost5.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: msg5} - if rup5, err := Client.UpdatePost(up5); err != nil { - t.Fatal(err) - } else { - if rup5.Data.(*model.Post).Message != up5.Message { - t.Fatal("failed to updates") - } - } - - time.Sleep(1000 * time.Millisecond) - - up6 := &model.Post{Id: rpost5.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: "zz" + model.NewId() + " update post 5"} - if _, err := Client.UpdatePost(up6); err == nil { - t.Fatal("shouldn't have been able to update a message after time limit") - } -} - -func TestGetPosts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id} - post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post3.Id} - post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post) - - r1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) - - if r1.Order[0] != post3a1.Id { - t.Fatal("wrong order") - } - - if r1.Order[1] != post3.Id { - t.Fatal("wrong order") - } - - if len(r1.Posts) != 2 { // 3a1 and 3; 3a1's parent already there - t.Fatal("wrong size") - } - - r2 := Client.Must(Client.GetPosts(channel1.Id, 2, 2, "")).Data.(*model.PostList) - - if r2.Order[0] != post2.Id { - t.Fatal("wrong order") - } - - if r2.Order[1] != post1a1.Id { - t.Fatal("wrong order") - } - - if len(r2.Posts) != 3 { // 2 and 1a1; + 1a1's parent - t.Log(r2.Posts) - t.Fatal("wrong size") - } -} - -func TestGetPostsSince(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - time.Sleep(10 * time.Millisecond) - post0 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post0 = Client.Must(Client.CreatePost(post0)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id} - post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post3.Id} - post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post) - - r1 := Client.Must(Client.GetPostsSince(channel1.Id, post1.CreateAt)).Data.(*model.PostList) - - if r1.Order[0] != post3a1.Id { - t.Fatal("wrong order") - } - - if r1.Order[1] != post3.Id { - t.Fatal("wrong order") - } - - if len(r1.Posts) != 5 { - t.Fatal("wrong size") - } - - now := model.GetMillis() - r2 := Client.Must(Client.GetPostsSince(channel1.Id, now)).Data.(*model.PostList) - - if len(r2.Posts) != 0 { - t.Fatal("should have been empty") - } - - post2.Message = "new message" - Client.Must(Client.UpdatePost(post2)) - - r3 := Client.Must(Client.GetPostsSince(channel1.Id, now)).Data.(*model.PostList) - - if len(r3.Order) != 2 { // 2 because deleted post is returned as well - t.Fatal("missing post update") - } -} - -func TestGetPostsBeforeAfter(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - time.Sleep(10 * time.Millisecond) - post0 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post0 = Client.Must(Client.CreatePost(post0)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id} - post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post3.Id} - post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post) - - r1 := Client.Must(Client.GetPostsBefore(channel1.Id, post1a1.Id, 0, 10, "")).Data.(*model.PostList) - - if r1.Order[0] != post1.Id { - t.Fatal("wrong order") - } - - if r1.Order[1] != post0.Id { - t.Fatal("wrong order") - } - - // including created post from test helper and system 'joined' message - if len(r1.Posts) != 4 { - t.Fatal("wrong size") - } - - r2 := Client.Must(Client.GetPostsAfter(channel1.Id, post3a1.Id, 0, 3, "")).Data.(*model.PostList) - - if len(r2.Posts) != 0 { - t.Fatal("should have been empty") - } - - post2.Message = "new message" - Client.Must(Client.UpdatePost(post2)) - - r3 := Client.Must(Client.GetPostsAfter(channel1.Id, post1a1.Id, 0, 2, "")).Data.(*model.PostList) - - if r3.Order[0] != post3.Id { - t.Fatal("wrong order") - } - - if r3.Order[1] != post2.Id { - t.Fatal("wrong order") - } - - if len(r3.Order) != 2 { - t.Fatal("missing post update") - } -} - -func TestSearchPosts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - post1 := &model.Post{ChannelId: channel1.Id, Message: "search for post1"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - post2 := &model.Post{ChannelId: channel1.Id, Message: "search for post2"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - post3 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag search for post3"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - post4 := &model.Post{ChannelId: channel1.Id, Message: "hashtag for post4"} - post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) - - r1 := Client.Must(Client.SearchPosts("search", false)).Data.(*model.PostList) - - if len(r1.Order) != 3 { - t.Fatal("wrong search") - } - - r2 := Client.Must(Client.SearchPosts("post2", false)).Data.(*model.PostList) - - if len(r2.Order) != 1 && r2.Order[0] == post2.Id { - t.Fatal("wrong search") - } - - r3 := Client.Must(Client.SearchPosts("#hashtag", false)).Data.(*model.PostList) - - if len(r3.Order) != 1 && r3.Order[0] == post3.Id { - t.Fatal("wrong search") - } - - if r4 := Client.Must(Client.SearchPosts("*", false)).Data.(*model.PostList); len(r4.Order) != 0 { - t.Fatal("searching for just * shouldn't return any results") - } - - r5 := Client.Must(Client.SearchPosts("post1 post2", true)).Data.(*model.PostList) - - if len(r5.Order) != 2 { - t.Fatal("wrong search results") - } -} - -func TestSearchHashtagPosts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - post1 := &model.Post{ChannelId: channel1.Id, Message: "#sgtitlereview with space"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - post2 := &model.Post{ChannelId: channel1.Id, Message: "#sgtitlereview\n with return"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - post3 := &model.Post{ChannelId: channel1.Id, Message: "no hashtag"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - r1 := Client.Must(Client.SearchPosts("#sgtitlereview", false)).Data.(*model.PostList) - - if len(r1.Order) != 2 { - t.Fatal("wrong search") - } -} - -func TestSearchPostsInChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - team := th.BasicTeam - - post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - channel2 := &model.Channel{DisplayName: "TestGetPosts", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - - channel3 := &model.Channel{DisplayName: "TestGetPosts", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - - post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - post3 := &model.Post{ChannelId: channel2.Id, Message: "other message with no return"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - post4 := &model.Post{ChannelId: channel3.Id, Message: "other message with no return"} - post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) - - if result := Client.Must(Client.SearchPosts("channel:", false)).Data.(*model.PostList); len(result.Order) != 0 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("in:", false)).Data.(*model.PostList); len(result.Order) != 0 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("channel:"+channel1.Name, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("in: "+channel2.Name, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("channel: "+channel2.Name, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("ChAnNeL: "+channel2.Name, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("sgtitlereview", false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("sgtitlereview channel:"+channel1.Name, false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("sgtitlereview in: "+channel2.Name, false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("sgtitlereview channel: "+channel2.Name, false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("channel: "+channel2.Name+" channel: "+channel3.Name, false)).Data.(*model.PostList); len(result.Order) != 3 { - t.Fatalf("wrong number of posts returned :) %v :) %v", result.Posts, result.Order) - } -} - -func TestSearchPostsFromUser(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - team := th.BasicTeam - user1 := th.BasicUser - user2 := th.BasicUser2 - channel2 := th.CreateChannel(Client, team) - Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser2.Id)) - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser2.Id)) - user3 := th.CreateUser(Client) - th.LinkUserToTeam(user3, team) - Client.Must(Client.AddChannelMember(channel1.Id, user3.Id)) - Client.Must(Client.AddChannelMember(channel2.Id, user3.Id)) - - post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - th.LoginBasic2() - - post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - if result := Client.Must(Client.SearchPosts("from: "+user1.Username, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username, false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username+" sgtitlereview", false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - post3 := &model.Post{ChannelId: channel1.Id, Message: "hullo"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username+" in:"+channel1.Name, false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - Client.Login(user3.Email, user3.Password) - - // wait for the join/leave messages to be created for user3 since they're done asynchronously - time.Sleep(100 * time.Millisecond) - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username+" from: "+user3.Username, false)).Data.(*model.PostList); len(result.Order) != 2 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username+" from: "+user3.Username+" in:"+channel2.Name, false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } - - post4 := &model.Post{ChannelId: channel2.Id, Message: "coconut"} - post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) - - if result := Client.Must(Client.SearchPosts("from: "+user2.Username+" from: "+user3.Username+" in:"+channel2.Name+" coconut", false)).Data.(*model.PostList); len(result.Order) != 1 { - t.Fatalf("wrong number of posts returned %v", len(result.Order)) - } -} - -func TestGetPostsCache(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - etag := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Etag - - // test etag caching - if cache_result, err := Client.GetPosts(channel1.Id, 0, 2, etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(*model.PostList) != nil { - t.Log(cache_result.Data) - t.Fatal("cache should be empty") - } - - etag = Client.Must(Client.GetPost(channel1.Id, post1.Id, "")).Etag - - // test etag caching - if cache_result, err := Client.GetPost(channel1.Id, post1.Id, etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(*model.PostList) != nil { - t.Log(cache_result.Data) - t.Fatal("cache should be empty") - } - -} - -func TestDeletePosts(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - team1 := th.BasicTeam - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id} - post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post1a2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id, ParentId: post1a1.Id} - post1a2 = Client.Must(Client.CreatePost(post1a2)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post3a1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: post3.Id} - post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - Client.Must(Client.DeletePost(channel1.Id, post3.Id)) - - r2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList) - - if post := r2.Posts[post3.Id]; post != nil { - t.Fatal("should have not returned deleted post") - } - - time.Sleep(10 * time.Millisecond) - post4a := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post4a = Client.Must(Client.CreatePost(post4a)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post4b := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post4b = Client.Must(Client.CreatePost(post4b)).Data.(*model.Post) - - SystemAdminClient := th.SystemAdminClient - th.LinkUserToTeam(th.SystemAdminUser, th.BasicTeam) - SystemAdminClient.Must(SystemAdminClient.JoinChannel(channel1.Id)) - - th.LoginBasic2() - Client.Must(Client.JoinChannel(channel1.Id)) - - if _, err := Client.DeletePost(channel1.Id, post4a.Id); err == nil { - t.Fatal(err) - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) - - Client.Logout() - th.LoginBasic2() - Client.SetTeamId(team1.Id) - - Client.Must(Client.DeletePost(channel1.Id, post4a.Id)) - - SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post4b.Id)) - - th.RemovePermissionFromRole(model.PERMISSION_DELETE_POST.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_POST.Id, model.TEAM_ADMIN_ROLE_ID) - - th.LoginBasic() - - time.Sleep(10 * time.Millisecond) - post5a := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post5a = Client.Must(Client.CreatePost(post5a)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post5b := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post5b = Client.Must(Client.CreatePost(post5b)).Data.(*model.Post) - - if _, err := Client.DeletePost(channel1.Id, post5a.Id); err == nil { - t.Fatal(err) - } - - th.LoginBasic2() - - Client.Must(Client.DeletePost(channel1.Id, post5a.Id)) - - SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post5b.Id)) -} - -func TestEmailMention(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser2.Id)) - - th.LoginBasic2() - //Set the notification properties - data := make(map[string]string) - data["user_id"] = th.BasicUser2.Id - data["email"] = "true" - data["desktop"] = "all" - data["desktop_sound"] = "false" - data["comments"] = "any" - Client.Must(Client.UpdateUserNotify(data)) - - store.Must(th.App.Srv.Store.Preference().Save(&model.Preferences{{ - UserId: th.BasicUser2.Id, - Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS, - Name: model.PREFERENCE_NAME_EMAIL_INTERVAL, - Value: "0", - }})) - - //Delete all the messages before create a mention post - utils.DeleteMailBox(th.BasicUser2.Email) - - //Send a mention message from user1 to user2 - th.LoginBasic() - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "@" + th.BasicUser2.Username + " this is a test"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - var resultsMailbox utils.JSONMessageHeaderInbucket - err := utils.RetryInbucket(5, func() error { - var err error - resultsMailbox, err = utils.GetMailBox(th.BasicUser2.Email) - return err - }) - if err != nil { - t.Log(err) - t.Log("No email was received, maybe due load on the server. Disabling this verification") - } - if err == nil && len(resultsMailbox) > 0 { - if !strings.ContainsAny(resultsMailbox[len(resultsMailbox)-1].To[0], th.BasicUser2.Email) { - t.Fatal("Wrong To recipient") - } else { - for i := 0; i < 30; i++ { - for j := len(resultsMailbox) - 1; j >= 0; j-- { - isUser := false - for _, to := range resultsMailbox[j].To { - if to == "<"+th.BasicUser2.Email+">" { - isUser = true - } - } - if !isUser { - continue - } - if resultsEmail, err := utils.GetMessageFromMailbox(th.BasicUser2.Email, resultsMailbox[j].ID); err == nil { - if strings.Contains(resultsEmail.Body.Text, post1.Message) { - return - } else if i == 4 { - t.Log(resultsEmail.Body.Text) - t.Fatal("Received wrong Message") - } - } - } - time.Sleep(100 * time.Millisecond) - } - t.Fatal("Didn't receive message") - } - } -} - -func TestFuzzyPosts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - for i := 0; i < len(utils.FUZZY_STRINGS_POSTS); i++ { - post := &model.Post{ChannelId: channel1.Id, Message: utils.FUZZY_STRINGS_POSTS[i]} - - _, err := Client.CreatePost(post) - if err != nil { - t.Fatal(err) - } - } -} - -func TestGetFlaggedPosts(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user1 := th.BasicUser - post1 := th.BasicPost - - preferences := &model.Preferences{ - { - UserId: user1.Id, - Category: model.PREFERENCE_CATEGORY_FLAGGED_POST, - Name: post1.Id, - Value: "true", - }, - } - Client.Must(Client.SetPreferences(preferences)) - - r1 := Client.Must(Client.GetFlaggedPosts(0, 2)).Data.(*model.PostList) - - if len(r1.Order) == 0 { - t.Fatal("should have gotten a flagged post") - } - - if _, ok := r1.Posts[post1.Id]; !ok { - t.Fatal("missing flagged post") - } - - Client.DeletePreferences(preferences) - - r2 := Client.Must(Client.GetFlaggedPosts(0, 2)).Data.(*model.PostList) - - if len(r2.Order) != 0 { - t.Fatal("should not have gotten a flagged post") - } - - Client.SetTeamId(model.NewId()) - if _, err := Client.GetFlaggedPosts(0, 2); err == nil { - t.Fatal("should have failed - bad team id") - } -} - -func TestGetMessageForNotification(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - testPng := store.Must(th.App.Srv.Store.FileInfo().Save(&model.FileInfo{ - CreatorId: model.NewId(), - Path: "test1.png", - Name: "test1.png", - MimeType: "image/png", - })).(*model.FileInfo) - - testJpg1 := store.Must(th.App.Srv.Store.FileInfo().Save(&model.FileInfo{ - CreatorId: model.NewId(), - Path: "test2.jpg", - Name: "test2.jpg", - MimeType: "image/jpeg", - })).(*model.FileInfo) - - testFile := store.Must(th.App.Srv.Store.FileInfo().Save(&model.FileInfo{ - CreatorId: model.NewId(), - Path: "test1.go", - Name: "test1.go", - MimeType: "text/plain", - })).(*model.FileInfo) - - testJpg2 := store.Must(th.App.Srv.Store.FileInfo().Save(&model.FileInfo{ - CreatorId: model.NewId(), - Path: "test3.jpg", - Name: "test3.jpg", - MimeType: "image/jpeg", - })).(*model.FileInfo) - - translateFunc := utils.GetUserTranslations("en") - - post := &model.Post{ - Id: model.NewId(), - Message: "test", - } - - if th.App.GetMessageForNotification(post, translateFunc) != "test" { - t.Fatal("should've returned message text") - } - - post.FileIds = model.StringArray{testPng.Id} - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(testPng.Id, post.Id)) - if th.App.GetMessageForNotification(post, translateFunc) != "test" { - t.Fatal("should've returned message text, even with attachments") - } - - post.Message = "" - if message := th.App.GetMessageForNotification(post, translateFunc); message != "1 image sent: test1.png" { - t.Fatal("should've returned number of images:", message) - } - - post.FileIds = model.StringArray{testPng.Id, testJpg1.Id} - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(testJpg1.Id, post.Id)) - th.App.Srv.Store.FileInfo().InvalidateFileInfosForPostCache(post.Id) - if message := th.App.GetMessageForNotification(post, translateFunc); message != "2 images sent: test1.png, test2.jpg" && message != "2 images sent: test2.jpg, test1.png" { - t.Fatal("should've returned number of images:", message) - } - - post.Id = model.NewId() - post.FileIds = model.StringArray{testFile.Id} - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(testFile.Id, post.Id)) - if message := th.App.GetMessageForNotification(post, translateFunc); message != "1 file sent: test1.go" { - t.Fatal("should've returned number of files:", message) - } - - store.Must(th.App.Srv.Store.FileInfo().AttachToPost(testJpg2.Id, post.Id)) - th.App.Srv.Store.FileInfo().InvalidateFileInfosForPostCache(post.Id) - post.FileIds = model.StringArray{testFile.Id, testJpg2.Id} - if message := th.App.GetMessageForNotification(post, translateFunc); message != "2 files sent: test1.go, test3.jpg" && message != "2 files sent: test3.jpg, test1.go" { - t.Fatal("should've returned number of mixed files:", message) - } -} - -func TestGetFileInfosForPost(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - fileIds := make([]string, 3) - if data, err := readTestFile("test.png"); err != nil { - t.Fatal(err) - } else { - for i := 0; i < 3; i++ { - fileIds[i] = Client.MustGeneric(Client.UploadPostAttachment(data, channel1.Id, "test.png")).(*model.FileUploadResponse).FileInfos[0].Id - } - } - - post1 := Client.Must(Client.CreatePost(&model.Post{ - ChannelId: channel1.Id, - Message: "test", - FileIds: fileIds, - })).Data.(*model.Post) - - var etag string - if infos, err := Client.GetFileInfosForPost(channel1.Id, post1.Id, ""); err != nil { - t.Fatal(err) - } else if len(infos) != 3 { - t.Fatal("should've received 3 files") - } else if Client.Etag == "" { - t.Fatal("should've received etag") - } else { - etag = Client.Etag - } - - if infos, err := Client.GetFileInfosForPost(channel1.Id, post1.Id, etag); err != nil { - t.Fatal(err) - } else if len(infos) != 0 { - t.Fatal("should've returned nothing because of etag") - } -} - -func TestGetPostById(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "yommamma" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - if post, respMetadata := Client.GetPostById(post1.Id, ""); respMetadata.Error != nil { - t.Fatal(respMetadata.Error) - } else { - if len(post.Order) != 1 { - t.Fatal("should be just one post") - } - - if post.Order[0] != post1.Id { - t.Fatal("wrong order") - } - - if post.Posts[post.Order[0]].Message != post1.Message { - t.Fatal("wrong message from post") - } - } - - if _, respMetadata := Client.GetPostById("45345435345345", ""); respMetadata.Error == nil { - t.Fatal(respMetadata.Error) - } -} - -func TestGetPermalinkTmp(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - channel1 := th.BasicChannel - team := th.BasicTeam - - th.LoginBasic() - - time.Sleep(10 * time.Millisecond) - post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - time.Sleep(10 * time.Millisecond) - post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - etag := Client.Must(Client.GetPost(channel1.Id, post1.Id, "")).Etag - - // test etag caching - if cache_result, respMetadata := Client.GetPermalink(channel1.Id, post1.Id, etag); respMetadata.Error != nil { - t.Fatal(respMetadata.Error) - } else if cache_result != nil { - t.Log(cache_result) - t.Fatal("cache should be empty") - } - - if results, respMetadata := Client.GetPermalink(channel1.Id, post1.Id, ""); respMetadata.Error != nil { - t.Fatal(respMetadata.Error) - } else if results == nil { - t.Fatal("should not be empty") - } - - // Test permalink to private channels. - channel2 := &model.Channel{DisplayName: "TestGetPermalinkPriv", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - time.Sleep(10 * time.Millisecond) - post3 := &model.Post{ChannelId: channel2.Id, Message: "zz" + model.NewId() + "a"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - if _, md := Client.GetPermalink(channel2.Id, post3.Id, ""); md.Error != nil { - t.Fatal(md.Error) - } - - th.LoginBasic2() - - if _, md := Client.GetPermalink(channel2.Id, post3.Id, ""); md.Error == nil { - t.Fatal("Expected 403 error") - } - - // Test direct channels. - th.LoginBasic() - channel3 := Client.Must(Client.CreateDirectChannel(th.SystemAdminUser.Id)).Data.(*model.Channel) - time.Sleep(10 * time.Millisecond) - post4 := &model.Post{ChannelId: channel3.Id, Message: "zz" + model.NewId() + "a"} - post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) - - if _, md := Client.GetPermalink(channel3.Id, post4.Id, ""); md.Error != nil { - t.Fatal(md.Error) - } - - th.LoginBasic2() - - if _, md := Client.GetPermalink(channel3.Id, post4.Id, ""); md.Error == nil { - t.Fatal("Expected 403 error") - } -} - -func TestGetOpenGraphMetadata(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.EnableLinkPreviews = true - *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" - }) - - ogDataCacheMissCount := 0 - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ogDataCacheMissCount++ - - if r.URL.Path == "/og-data/" { - fmt.Fprintln(w, ` - - - - - `) - } else if r.URL.Path == "/no-og-data/" { - fmt.Fprintln(w, ``) - } - })) - - for _, data := range [](map[string]interface{}){ - {"path": "/og-data/", "title": "Test Title", "cacheMissCount": 1}, - {"path": "/no-og-data/", "title": "", "cacheMissCount": 2}, - - // Data should be cached for following - {"path": "/og-data/", "title": "Test Title", "cacheMissCount": 2}, - {"path": "/no-og-data/", "title": "", "cacheMissCount": 2}, - } { - res, err := Client.DoApiPost( - "/get_opengraph_metadata", - fmt.Sprintf("{\"url\":\"%s\"}", ts.URL+data["path"].(string)), - ) - if err != nil { - t.Fatal(err) - } - - ogData := model.StringInterfaceFromJson(res.Body) - if strings.Compare(ogData["title"].(string), data["title"].(string)) != 0 { - t.Fatal(fmt.Sprintf( - "OG data title mismatch for path \"%s\". Expected title: \"%s\". Actual title: \"%s\"", - data["path"].(string), data["title"].(string), ogData["title"].(string), - )) - } - - if ogDataCacheMissCount != data["cacheMissCount"].(int) { - t.Fatal(fmt.Sprintf( - "Cache miss count didn't match. Expected value %d. Actual value %d.", - data["cacheMissCount"].(int), ogDataCacheMissCount, - )) - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableLinkPreviews = false }) - if _, err := Client.DoApiPost("/get_opengraph_metadata", "{\"url\":\"/og-data/\"}"); err == nil || err.StatusCode != http.StatusNotImplemented { - t.Fatal("should have failed with 501 - disabled link previews") - } -} - -func TestPinPost(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - post := th.BasicPost - if rupost1, err := Client.PinPost(post.ChannelId, post.Id); err != nil { - t.Fatal(err) - } else { - if !rupost1.Data.(*model.Post).IsPinned { - t.Fatal("failed to pin post") - } - } - - pinnedPost := th.PinnedPost - if rupost2, err := Client.PinPost(pinnedPost.ChannelId, pinnedPost.Id); err != nil { - t.Fatal(err) - } else { - if !rupost2.Data.(*model.Post).IsPinned { - t.Fatal("pinning a post should be idempotent") - } - } -} - -func TestUnpinPost(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - pinnedPost := th.PinnedPost - if rupost1, err := Client.UnpinPost(pinnedPost.ChannelId, pinnedPost.Id); err != nil { - t.Fatal(err) - } else { - if rupost1.Data.(*model.Post).IsPinned { - t.Fatal("failed to unpin post") - } - } - - post := th.BasicPost - if rupost2, err := Client.UnpinPost(post.ChannelId, post.Id); err != nil { - t.Fatal(err) - } else { - if rupost2.Data.(*model.Post).IsPinned { - t.Fatal("unpinning a post should be idempotent") - } - } -} diff --git a/api/preference.go b/api/preference.go deleted file mode 100644 index 8de1ea453..000000000 --- a/api/preference.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitPreference() { - api.BaseRoutes.Preferences.Handle("/", api.ApiUserRequired(getAllPreferences)).Methods("GET") - api.BaseRoutes.Preferences.Handle("/save", api.ApiUserRequired(savePreferences)).Methods("POST") - api.BaseRoutes.Preferences.Handle("/delete", api.ApiUserRequired(deletePreferences)).Methods("POST") - api.BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}", api.ApiUserRequired(getPreferenceCategory)).Methods("GET") - api.BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}/{name:[A-Za-z0-9_]+}", api.ApiUserRequired(getPreference)).Methods("GET") -} - -func getAllPreferences(c *Context, w http.ResponseWriter, r *http.Request) { - if result := <-c.App.Srv.Store.Preference().GetAll(c.Session.UserId); result.Err != nil { - c.Err = result.Err - } else { - data := result.Data.(model.Preferences) - - w.Write([]byte(data.ToJson())) - } -} - -func savePreferences(c *Context, w http.ResponseWriter, r *http.Request) { - preferences, err := model.PreferencesFromJson(r.Body) - if err != nil { - c.Err = model.NewAppError("savePreferences", "api.preference.save_preferences.decode.app_error", nil, err.Error(), http.StatusBadRequest) - return - } - - if err := c.App.UpdatePreferences(c.Session.UserId, preferences); err != nil { - c.Err = err - return - } - - w.Write([]byte("true")) -} - -func getPreferenceCategory(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - category := params["category"] - - if result := <-c.App.Srv.Store.Preference().GetCategory(c.Session.UserId, category); result.Err != nil { - c.Err = result.Err - } else { - data := result.Data.(model.Preferences) - - w.Write([]byte(data.ToJson())) - } -} - -func getPreference(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - category := params["category"] - name := params["name"] - - if result := <-c.App.Srv.Store.Preference().Get(c.Session.UserId, category, name); result.Err != nil { - c.Err = result.Err - } else { - data := result.Data.(model.Preference) - w.Write([]byte(data.ToJson())) - } -} - -func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) { - preferences, err := model.PreferencesFromJson(r.Body) - if err != nil { - c.Err = model.NewAppError("savePreferences", "api.preference.delete_preferences.decode.app_error", nil, err.Error(), http.StatusBadRequest) - return - } - - if err := c.App.DeletePreferences(c.Session.UserId, preferences); err != nil { - c.Err = err - return - } - - ReturnStatusOK(w) -} diff --git a/api/preference_test.go b/api/preference_test.go deleted file mode 100644 index 3948ca2c9..000000000 --- a/api/preference_test.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestGetAllPreferences(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user1 := th.BasicUser - - category := model.NewId() - - preferences1 := model.Preferences{ - { - UserId: user1.Id, - Category: category, - Name: model.NewId(), - }, - { - UserId: user1.Id, - Category: category, - Name: model.NewId(), - }, - { - UserId: user1.Id, - Category: model.NewId(), - Name: model.NewId(), - }, - } - - Client.Must(Client.SetPreferences(&preferences1)) - - if result, err := Client.GetAllPreferences(); err != nil { - t.Fatal(err) - } else if data := result.Data.(model.Preferences); len(data) != 4 { - t.Fatal("received the wrong number of preferences") - } - - th.LoginBasic2() - - if result, err := Client.GetAllPreferences(); err != nil { - t.Fatal(err) - } else if data := result.Data.(model.Preferences); len(data) == 0 { - t.Fatal("received the wrong number of preferences") - } -} - -func TestSetPreferences(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user1 := th.BasicUser - - // save 10 preferences - var preferences model.Preferences - for i := 0; i < 10; i++ { - preference := model.Preference{ - UserId: user1.Id, - Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, - Name: model.NewId(), - } - preferences = append(preferences, preference) - } - - if _, err := Client.SetPreferences(&preferences); err != nil { - t.Fatal(err) - } - - // update 10 preferences - for _, preference := range preferences { - preference.Value = "1234garbage" - } - - if _, err := Client.SetPreferences(&preferences); err != nil { - t.Fatal(err) - } - - th.LoginBasic2() - - if _, err := Client.SetPreferences(&preferences); err == nil { - t.Fatal("shouldn't have been able to update another user's preferences") - } -} - -func TestGetPreferenceCategory(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user1 := th.BasicUser - - category := model.NewId() - - preferences1 := model.Preferences{ - { - UserId: user1.Id, - Category: category, - Name: model.NewId(), - }, - { - UserId: user1.Id, - Category: category, - Name: model.NewId(), - }, - { - UserId: user1.Id, - Category: model.NewId(), - Name: model.NewId(), - }, - } - - Client.Must(Client.SetPreferences(&preferences1)) - - if result, err := Client.GetPreferenceCategory(category); err != nil { - t.Fatal(err) - } else if data := result.Data.(model.Preferences); len(data) != 2 { - t.Fatal("received the wrong number of preferences") - } else if !((data[0] == preferences1[0] && data[1] == preferences1[1]) || (data[0] == preferences1[1] && data[1] == preferences1[0])) { - t.Fatal("received incorrect preferences") - } - - th.LoginBasic2() - - if result, err := Client.GetPreferenceCategory(category); err != nil { - t.Fatal(err) - } else if data := result.Data.(model.Preferences); len(data) != 0 { - t.Fatal("received the wrong number of preferences") - } -} - -func TestGetPreference(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user := th.BasicUser - - preferences := model.Preferences{ - { - UserId: user.Id, - Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, - Name: model.NewId(), - Value: model.NewId(), - }, - } - - Client.Must(Client.SetPreferences(&preferences)) - - if result, err := Client.GetPreference(preferences[0].Category, preferences[0].Name); err != nil { - t.Fatal(err) - } else if data := result.Data.(*model.Preference); *data != preferences[0] { - t.Fatal("preference saved incorrectly") - } - - preferences[0].Value = model.NewId() - Client.Must(Client.SetPreferences(&preferences)) - - if result, err := Client.GetPreference(preferences[0].Category, preferences[0].Name); err != nil { - t.Fatal(err) - } else if data := result.Data.(*model.Preference); *data != preferences[0] { - t.Fatal("preference updated incorrectly") - } -} - -func TestDeletePreferences(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user1 := th.BasicUser - - var originalCount int - if result, err := Client.GetAllPreferences(); err != nil { - t.Fatal(err) - } else { - originalCount = len(result.Data.(model.Preferences)) - } - - // save 10 preferences - var preferences model.Preferences - for i := 0; i < 10; i++ { - preference := model.Preference{ - UserId: user1.Id, - Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, - Name: model.NewId(), - } - preferences = append(preferences, preference) - } - - if _, err := Client.SetPreferences(&preferences); err != nil { - t.Fatal(err) - } - - // delete 10 preferences - th.LoginBasic2() - - if _, err := Client.DeletePreferences(&preferences); err == nil { - t.Fatal("shouldn't have been able to delete another user's preferences") - } - - th.LoginBasic() - if _, err := Client.DeletePreferences(&preferences); err != nil { - t.Fatal(err) - } - - if result, err := Client.GetAllPreferences(); err != nil { - t.Fatal(err) - } else if data := result.Data.(model.Preferences); len(data) != originalCount { - t.Fatal("should've deleted preferences") - } -} diff --git a/api/reaction.go b/api/reaction.go deleted file mode 100644 index 812c9f582..000000000 --- a/api/reaction.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitReaction() { - api.BaseRoutes.NeedPost.Handle("/reactions/save", api.ApiUserRequired(saveReaction)).Methods("POST") - api.BaseRoutes.NeedPost.Handle("/reactions/delete", api.ApiUserRequired(deleteReaction)).Methods("POST") - api.BaseRoutes.NeedPost.Handle("/reactions", api.ApiUserRequired(listReactions)).Methods("GET") -} - -func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) { - reaction := model.ReactionFromJson(r.Body) - if reaction == nil { - c.SetInvalidParam("saveReaction", "reaction") - return - } - - if reaction.UserId != c.Session.UserId { - c.Err = model.NewAppError("saveReaction", "api.reaction.save_reaction.user_id.app_error", nil, "", http.StatusForbidden) - return - } - - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("saveReaction", "channelId") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_ADD_REACTION) { - c.SetPermissionError(model.PERMISSION_ADD_REACTION) - return - } - - postId := params["post_id"] - if len(postId) != 26 || postId != reaction.PostId { - c.SetInvalidParam("saveReaction", "postId") - return - } - - var post *model.Post - - if result := <-c.App.Srv.Store.Post().Get(reaction.PostId); result.Err != nil { - c.Err = result.Err - return - } else if post = result.Data.(*model.PostList).Posts[postId]; post.ChannelId != channelId { - c.Err = model.NewAppError("saveReaction", "api.reaction.save_reaction.mismatched_channel_id.app_error", - nil, "channelId="+channelId+", post.ChannelId="+post.ChannelId+", postId="+postId, http.StatusBadRequest) - return - } - - if reaction, err := c.App.SaveReactionForPost(reaction); err != nil { - c.Err = err - return - } else { - w.Write([]byte(reaction.ToJson())) - return - } -} - -func deleteReaction(c *Context, w http.ResponseWriter, r *http.Request) { - reaction := model.ReactionFromJson(r.Body) - if reaction == nil { - c.SetInvalidParam("deleteReaction", "reaction") - return - } - - if reaction.UserId != c.Session.UserId { - c.Err = model.NewAppError("deleteReaction", "api.reaction.delete_reaction.user_id.app_error", nil, "", http.StatusForbidden) - return - } - - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("deleteReaction", "channelId") - return - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_REMOVE_REACTION) { - c.SetPermissionError(model.PERMISSION_REMOVE_REACTION) - return - } - - postId := params["post_id"] - if len(postId) != 26 || postId != reaction.PostId { - c.SetInvalidParam("deleteReaction", "postId") - return - } - - err := c.App.DeleteReactionForPost(reaction) - if err != nil { - c.Err = err - return - } - - ReturnStatusOK(w) -} - -func listReactions(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - channelId := params["channel_id"] - if len(channelId) != 26 { - c.SetInvalidParam("deletePost", "channelId") - return - } - - postId := params["post_id"] - if len(postId) != 26 { - c.SetInvalidParam("listReactions", "postId") - return - } - - pchan := c.App.Srv.Store.Post().Get(postId) - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if result := <-pchan; result.Err != nil { - c.Err = result.Err - return - } else if post := result.Data.(*model.PostList).Posts[postId]; post.ChannelId != channelId { - c.Err = model.NewAppError("listReactions", "api.reaction.list_reactions.mismatched_channel_id.app_error", - nil, "channelId="+channelId+", post.ChannelId="+post.ChannelId+", postId="+postId, http.StatusBadRequest) - return - } - - if result := <-c.App.Srv.Store.Reaction().GetForPost(postId, true); result.Err != nil { - c.Err = result.Err - return - } else { - reactions := result.Data.([]*model.Reaction) - - w.Write([]byte(model.ReactionsToJson(reactions))) - } -} diff --git a/api/reaction_test.go b/api/reaction_test.go deleted file mode 100644 index 23e02fb7a..000000000 --- a/api/reaction_test.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestSaveReaction(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - user := th.BasicUser - user2 := th.BasicUser2 - - channel := th.BasicChannel - post := th.BasicPost - - // saving a reaction - reaction := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - EmojiName: "smile", - } - if returned, err := Client.SaveReaction(channel.Id, reaction); err != nil { - t.Fatal(err) - } else { - reaction = returned - } - - if reactions := Client.MustGeneric(Client.ListReactions(channel.Id, post.Id)).([]*model.Reaction); len(reactions) != 1 || *reactions[0] != *reaction { - t.Fatal("didn't save reaction correctly") - } - - // saving a duplicate reaction - if _, err := Client.SaveReaction(channel.Id, reaction); err != nil { - t.Fatal(err) - } - - // saving a second reaction on a post - reaction2 := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - EmojiName: "sad", - } - if returned, err := Client.SaveReaction(channel.Id, reaction2); err != nil { - t.Fatal(err) - } else { - reaction2 = returned - } - - if reactions := Client.MustGeneric(Client.ListReactions(channel.Id, post.Id)).([]*model.Reaction); len(reactions) != 2 || - (*reactions[0] != *reaction && *reactions[1] != *reaction) || (*reactions[0] != *reaction2 && *reactions[1] != *reaction2) { - t.Fatal("didn't save multiple reactions correctly") - } - - // saving a reaction without a user id - reaction3 := &model.Reaction{ - PostId: post.Id, - EmojiName: "smile", - } - if _, err := Client.SaveReaction(channel.Id, reaction3); err == nil { - t.Fatal("should've failed to save reaction without user id") - } - - // saving a reaction without a post id - reaction4 := &model.Reaction{ - UserId: user.Id, - EmojiName: "smile", - } - if _, err := Client.SaveReaction(channel.Id, reaction4); err == nil { - t.Fatal("should've failed to save reaction without post id") - } - - // saving a reaction without a emoji name - reaction5 := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - } - if _, err := Client.SaveReaction(channel.Id, reaction5); err == nil { - t.Fatal("should've failed to save reaction without emoji name") - } - - // saving a reaction for another user - reaction6 := &model.Reaction{ - UserId: user2.Id, - PostId: post.Id, - EmojiName: "smile", - } - if _, err := Client.SaveReaction(channel.Id, reaction6); err == nil { - t.Fatal("should've failed to save reaction for another user") - } - - // saving a reaction to a channel we're not a member of - th.LoginBasic2() - channel2 := th.CreateChannel(th.BasicClient, th.BasicTeam) - post2 := th.CreatePost(th.BasicClient, channel2) - th.LoginBasic() - - reaction7 := &model.Reaction{ - UserId: user.Id, - PostId: post2.Id, - EmojiName: "smile", - } - if _, err := Client.SaveReaction(channel2.Id, reaction7); err == nil { - t.Fatal("should've failed to save reaction to a channel we're not a member of") - } - - // saving a reaction to a direct channel - directChannel := Client.Must(Client.CreateDirectChannel(user2.Id)).Data.(*model.Channel) - directPost := th.CreatePost(th.BasicClient, directChannel) - - reaction8 := &model.Reaction{ - UserId: user.Id, - PostId: directPost.Id, - EmojiName: "smile", - } - if returned, err := Client.SaveReaction(directChannel.Id, reaction8); err != nil { - t.Fatal(err) - } else { - reaction8 = returned - } - - if reactions := Client.MustGeneric(Client.ListReactions(directChannel.Id, directPost.Id)).([]*model.Reaction); len(reactions) != 1 || *reactions[0] != *reaction8 { - t.Fatal("didn't save reaction correctly") - } - - // saving a reaction for a post in the wrong channel - reaction9 := &model.Reaction{ - UserId: user.Id, - PostId: directPost.Id, - EmojiName: "sad", - } - if _, err := Client.SaveReaction(channel.Id, reaction9); err == nil { - t.Fatal("should've failed to save reaction to a post that isn't in the given channel") - } -} - -func TestDeleteReaction(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - user := th.BasicUser - user2 := th.BasicUser2 - - channel := th.BasicChannel - post := th.BasicPost - - reaction1 := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - EmojiName: "smile", - } - - // deleting a reaction that does exist - Client.MustGeneric(Client.SaveReaction(channel.Id, reaction1)) - if err := Client.DeleteReaction(channel.Id, reaction1); err != nil { - t.Fatal(err) - } - - if reactions := Client.MustGeneric(Client.ListReactions(channel.Id, post.Id)).([]*model.Reaction); len(reactions) != 0 { - t.Fatal("should've deleted reaction") - } - - // deleting one reaction when a post has multiple - reaction2 := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - EmojiName: "sad", - } - reaction1 = Client.MustGeneric(Client.SaveReaction(channel.Id, reaction1)).(*model.Reaction) - reaction2 = Client.MustGeneric(Client.SaveReaction(channel.Id, reaction2)).(*model.Reaction) - if err := Client.DeleteReaction(channel.Id, reaction2); err != nil { - t.Fatal(err) - } - - if reactions := Client.MustGeneric(Client.ListReactions(channel.Id, post.Id)).([]*model.Reaction); len(reactions) != 1 || *reactions[0] != *reaction1 { - t.Fatal("should've deleted only one reaction") - } - - // deleting a reaction made by another user - reaction3 := &model.Reaction{ - UserId: user2.Id, - PostId: post.Id, - EmojiName: "smile", - } - - th.LoginBasic2() - Client.Must(Client.JoinChannel(channel.Id)) - reaction3 = Client.MustGeneric(Client.SaveReaction(channel.Id, reaction3)).(*model.Reaction) - - th.LoginBasic() - if err := Client.DeleteReaction(channel.Id, reaction3); err == nil { - t.Fatal("should've failed to delete another user's reaction") - } - - // deleting a reaction for a post we can't see - channel2 := th.CreateChannel(th.BasicClient, th.BasicTeam) - post2 := th.CreatePost(th.BasicClient, channel2) - - reaction4 := &model.Reaction{ - UserId: user.Id, - PostId: post2.Id, - EmojiName: "smile", - } - - reaction4 = Client.MustGeneric(Client.SaveReaction(channel2.Id, reaction4)).(*model.Reaction) - Client.Must(Client.LeaveChannel(channel2.Id)) - - if err := Client.DeleteReaction(channel2.Id, reaction4); err == nil { - t.Fatal("should've failed to delete a reaction from a channel we're not in") - } - - // deleting a reaction for a post with the wrong channel - channel3 := th.CreateChannel(th.BasicClient, th.BasicTeam) - - reaction5 := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - EmojiName: "happy", - } - if _, err := Client.SaveReaction(channel3.Id, reaction5); err == nil { - t.Fatal("should've failed to save reaction to a post that isn't in the given channel") - } -} - -func TestListReactions(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - user := th.BasicUser - user2 := th.BasicUser2 - - channel := th.BasicChannel - - post := th.BasicPost - - userReactions := []*model.Reaction{ - { - UserId: user.Id, - PostId: post.Id, - EmojiName: "smile", - }, - { - UserId: user.Id, - PostId: post.Id, - EmojiName: "happy", - }, - { - UserId: user.Id, - PostId: post.Id, - EmojiName: "sad", - }, - } - - for i, reaction := range userReactions { - userReactions[i] = Client.MustGeneric(Client.SaveReaction(channel.Id, reaction)).(*model.Reaction) - } - - th.LoginBasic2() - Client.Must(Client.JoinChannel(channel.Id)) - - userReactions2 := []*model.Reaction{ - { - UserId: user2.Id, - PostId: post.Id, - EmojiName: "smile", - }, - { - UserId: user2.Id, - PostId: post.Id, - EmojiName: "sad", - }, - } - - for i, reaction := range userReactions2 { - userReactions2[i] = Client.MustGeneric(Client.SaveReaction(channel.Id, reaction)).(*model.Reaction) - } - - if reactions, err := Client.ListReactions(channel.Id, post.Id); err != nil { - t.Fatal(err) - } else if len(reactions) != 5 { - t.Fatal("should've returned 5 reactions") - } else { - checkForReaction := func(expected *model.Reaction) { - found := false - - for _, reaction := range reactions { - if *reaction == *expected { - found = true - break - } - } - - if !found { - t.Fatalf("didn't return expected reaction %v", *expected) - } - } - - for _, reaction := range userReactions { - checkForReaction(reaction) - } - - for _, reaction := range userReactions2 { - checkForReaction(reaction) - } - } -} diff --git a/api/status.go b/api/status.go deleted file mode 100644 index e1e4de522..000000000 --- a/api/status.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitStatus() { - api.BaseRoutes.Users.Handle("/status", api.ApiUserRequired(getStatusesHttp)).Methods("GET") - api.BaseRoutes.Users.Handle("/status/ids", api.ApiUserRequired(getStatusesByIdsHttp)).Methods("POST") -} - -func getStatusesHttp(c *Context, w http.ResponseWriter, r *http.Request) { - statusMap := model.StatusMapToInterfaceMap(c.App.GetAllStatuses()) - w.Write([]byte(model.StringInterfaceToJson(statusMap))) -} - -func getStatusesByIdsHttp(c *Context, w http.ResponseWriter, r *http.Request) { - userIds := model.ArrayFromJson(r.Body) - - if len(userIds) == 0 { - c.SetInvalidParam("getStatusesByIdsHttp", "user_ids") - return - } - - statusMap, err := c.App.GetStatusesByIds(userIds) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(model.StringInterfaceToJson(statusMap))) -} diff --git a/api/status_test.go b/api/status_test.go deleted file mode 100644 index 6a0ba3937..000000000 --- a/api/status_test.go +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - "testing" - "time" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" -) - -func TestStatuses(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - WebSocketClient, err := th.CreateWebSocketClient() - if err != nil { - t.Fatal(err) - } - defer WebSocketClient.Close() - WebSocketClient.Listen() - - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { - t.Fatal("should have responded OK to authentication challenge") - } - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) - th.LinkUserToTeam(ruser, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) - - user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User) - th.LinkUserToTeam(ruser2, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Id)) - - Client.Login(user.Email, user.Password) - Client.SetTeamId(team.Id) - - r1, err := Client.GetStatuses() - if err != nil { - t.Fatal(err) - } - - statuses := r1.Data.(map[string]string) - - for _, status := range statuses { - if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { - t.Fatal("one of the statuses had an invalid value") - } - } - - th.LoginBasic2() - - WebSocketClient2, err2 := th.CreateWebSocketClient() - if err2 != nil { - t.Fatal(err2) - } - - time.Sleep(1000 * time.Millisecond) - - WebSocketClient.GetStatuses() - if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { - t.Fatal(resp.Error) - } else { - if resp.SeqReply != WebSocketClient.Sequence-1 { - t.Fatal("bad sequence number") - } - - for _, status := range resp.Data { - if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { - t.Fatal("one of the statuses had an invalid value") - } - } - - if status, ok := resp.Data[th.BasicUser2.Id]; !ok { - t.Log(resp.Data) - t.Fatal("should have had user status") - } else if status != model.STATUS_ONLINE { - t.Log(status) - t.Fatal("status should have been online") - } - } - - WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id}) - if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { - t.Fatal(resp.Error) - } else { - if resp.SeqReply != WebSocketClient.Sequence-1 { - t.Fatal("bad sequence number") - } - - for _, status := range resp.Data { - if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { - t.Fatal("one of the statuses had an invalid value") - } - } - - if status, ok := resp.Data[th.BasicUser2.Id]; !ok { - t.Log(len(resp.Data)) - t.Fatal("should have had user status") - } else if status != model.STATUS_ONLINE { - t.Log(status) - t.Fatal("status should have been online") - } else if len(resp.Data) != 1 { - t.Fatal("only 1 status should be returned") - } - } - - WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"}) - if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { - t.Fatal(resp.Error) - } else { - if resp.SeqReply != WebSocketClient.Sequence-1 { - t.Fatal("bad sequence number") - } - - if len(resp.Data) != 2 { - t.Fatal("2 statuses should be returned") - } - } - - WebSocketClient.GetStatusesByIds([]string{}) - if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil { - if resp.SeqReply != WebSocketClient.Sequence-1 { - t.Fatal("bad sequence number") - } - t.Fatal("should have errored - empty user ids") - } - - WebSocketClient2.Close() - - th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) - - awayTimeout := *th.App.Config().TeamSettings.UserStatusAwayTimeout - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = awayTimeout }) - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = 1 }) - - time.Sleep(1500 * time.Millisecond) - - th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) - th.App.SetStatusOnline(th.BasicUser.Id, "junk", false) - - time.Sleep(1500 * time.Millisecond) - - WebSocketClient.GetStatuses() - if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { - t.Fatal(resp.Error) - } else { - if resp.SeqReply != WebSocketClient.Sequence-1 { - t.Fatal("bad sequence number") - } - - if _, ok := resp.Data[th.BasicUser2.Id]; ok { - t.Fatal("should not have had user status") - } - } - - stop := make(chan bool) - onlineHit := false - awayHit := false - - go func() { - for { - select { - case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { - status := resp.Data["status"].(string) - if status == model.STATUS_ONLINE { - onlineHit = true - } else if status == model.STATUS_AWAY { - awayHit = true - } - } - case <-stop: - return - } - } - }() - - time.Sleep(500 * time.Millisecond) - - stop <- true - - if !onlineHit { - t.Fatal("didn't get online event") - } - if !awayHit { - t.Fatal("didn't get away event") - } - - time.Sleep(500 * time.Millisecond) - - WebSocketClient.Close() -} - -func TestGetStatusesByIds(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if result, err := Client.GetStatusesByIds([]string{th.BasicUser.Id}); err != nil { - t.Fatal(err) - } else { - statuses := result.Data.(map[string]string) - if len(statuses) != 1 { - t.Fatal("should only have 1 status") - } - } - - if result, err := Client.GetStatusesByIds([]string{th.BasicUser.Id, th.BasicUser2.Id, "junk"}); err != nil { - t.Fatal(err) - } else { - statuses := result.Data.(map[string]string) - if len(statuses) != 3 { - t.Fatal("should have 3 statuses") - } - } - - if _, err := Client.GetStatusesByIds([]string{}); err == nil { - t.Fatal("should have errored") - } -} diff --git a/api/team.go b/api/team.go deleted file mode 100644 index c3eaab128..000000000 --- a/api/team.go +++ /dev/null @@ -1,543 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "io" - "net/http" - "strconv" - "strings" - - "github.com/gorilla/mux" - - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitTeam() { - api.BaseRoutes.Teams.Handle("/create", api.ApiUserRequired(createTeam)).Methods("POST") - api.BaseRoutes.Teams.Handle("/all", api.ApiUserRequired(getAll)).Methods("GET") - api.BaseRoutes.Teams.Handle("/all_team_listings", api.ApiUserRequired(GetAllTeamListings)).Methods("GET") - api.BaseRoutes.Teams.Handle("/get_invite_info", api.ApiAppHandler(getInviteInfo)).Methods("POST") - api.BaseRoutes.Teams.Handle("/find_team_by_name", api.ApiUserRequired(findTeamByName)).Methods("POST") - api.BaseRoutes.Teams.Handle("/name/{team_name:[A-Za-z0-9\\-]+}", api.ApiUserRequired(getTeamByName)).Methods("GET") - api.BaseRoutes.Teams.Handle("/members", api.ApiUserRequired(getMyTeamMembers)).Methods("GET") - api.BaseRoutes.Teams.Handle("/unread", api.ApiUserRequired(getMyTeamsUnread)).Methods("GET") - - api.BaseRoutes.NeedTeam.Handle("/me", api.ApiUserRequired(getMyTeam)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/stats", api.ApiUserRequired(getTeamStats)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/members/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getTeamMembers)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/members/ids", api.ApiUserRequired(getTeamMembersByIds)).Methods("POST") - api.BaseRoutes.NeedTeam.Handle("/members/{user_id:[A-Za-z0-9]+}", api.ApiUserRequired(getTeamMember)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/update", api.ApiUserRequired(updateTeam)).Methods("POST") - api.BaseRoutes.NeedTeam.Handle("/update_member_roles", api.ApiUserRequired(updateMemberRoles)).Methods("POST") - - api.BaseRoutes.NeedTeam.Handle("/invite_members", api.ApiUserRequired(inviteMembers)).Methods("POST") - - api.BaseRoutes.NeedTeam.Handle("/add_user_to_team", api.ApiUserRequired(addUserToTeam)).Methods("POST") - api.BaseRoutes.NeedTeam.Handle("/remove_user_from_team", api.ApiUserRequired(removeUserFromTeam)).Methods("POST") - - // These should be moved to the global admin console - api.BaseRoutes.NeedTeam.Handle("/import_team", api.ApiUserRequired(importTeam)).Methods("POST") - api.BaseRoutes.Teams.Handle("/add_user_to_team_from_invite", api.ApiUserRequiredMfa(addUserToTeamFromInvite)).Methods("POST") -} - -func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { - team := model.TeamFromJson(r.Body) - - if team == nil { - c.SetInvalidParam("createTeam", "team") - return - } - - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_CREATE_TEAM) { - c.Err = model.NewAppError("createTeam", "api.team.is_team_creation_allowed.disabled.app_error", nil, "", http.StatusForbidden) - return - } - - rteam, err := c.App.CreateTeamWithUser(team, c.Session.UserId) - if err != nil { - c.Err = err - return - } - - // Don't sanitize the team here since the user will be a team admin and their session won't reflect that yet - - w.Write([]byte(rteam.ToJson())) -} - -func GetAllTeamListings(c *Context, w http.ResponseWriter, r *http.Request) { - var teams []*model.Team - var err *model.AppError - - if teams, err = c.App.GetAllOpenTeams(); err != nil { - c.Err = err - return - } - - m := make(map[string]*model.Team) - for _, v := range teams { - m[v.Id] = v - } - - sanitizeTeamMap(c, m) - - w.Write([]byte(model.TeamMapToJson(m))) -} - -// Gets all teams which the current user can has access to. If the user is a System Admin, this will be all teams -// on the server. Otherwise, it will only be the teams of which the user is a member. -func getAll(c *Context, w http.ResponseWriter, r *http.Request) { - var teams []*model.Team - var err *model.AppError - - if c.App.HasPermissionTo(c.Session.UserId, model.PERMISSION_MANAGE_SYSTEM) { - teams, err = c.App.GetAllTeams() - } else { - teams, err = c.App.GetTeamsForUser(c.Session.UserId) - } - - if err != nil { - c.Err = err - return - } - - m := make(map[string]*model.Team) - for _, v := range teams { - m[v.Id] = v - } - - sanitizeTeamMap(c, m) - - w.Write([]byte(model.TeamMapToJson(m))) -} - -func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { - invites := model.InvitesFromJson(r.Body) - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_INVITE_USER) { - c.SetPermissionError(model.PERMISSION_INVITE_USER) - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_ADD_USER_TO_TEAM) { - c.SetPermissionError(model.PERMISSION_INVITE_USER) - return - } - - if err := c.App.InviteNewUsersToTeam(invites.ToEmailList(), c.TeamId, c.Session.UserId); err != nil { - c.Err = err - return - } - - w.Write([]byte(invites.ToJson())) -} - -func addUserToTeam(c *Context, w http.ResponseWriter, r *http.Request) { - params := model.MapFromJson(r.Body) - userId := params["user_id"] - - if len(userId) != 26 { - c.SetInvalidParam("addUserToTeam", "user_id") - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_ADD_USER_TO_TEAM) { - c.SetPermissionError(model.PERMISSION_ADD_USER_TO_TEAM) - return - } - - if _, err := c.App.AddUserToTeam(c.TeamId, userId, ""); err != nil { - c.Err = err - return - } - - w.Write([]byte(model.MapToJson(params))) -} - -func removeUserFromTeam(c *Context, w http.ResponseWriter, r *http.Request) { - params := model.MapFromJson(r.Body) - userId := params["user_id"] - - if len(userId) != 26 { - c.SetInvalidParam("removeUserFromTeam", "user_id") - return - } - - if c.Session.UserId != userId { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_REMOVE_USER_FROM_TEAM) { - c.SetPermissionError(model.PERMISSION_REMOVE_USER_FROM_TEAM) - return - } - } - - if err := c.App.RemoveUserFromTeam(c.TeamId, userId, c.Session.UserId); err != nil { - c.Err = err - return - } - - w.Write([]byte(model.MapToJson(params))) -} - -func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) { - params := model.MapFromJson(r.Body) - tokenId := params["token"] - inviteId := params["invite_id"] - - var team *model.Team - var err *model.AppError - - if len(tokenId) > 0 { - team, err = c.App.AddUserToTeamByToken(c.Session.UserId, tokenId) - } else if len(inviteId) > 0 { - team, err = c.App.AddUserToTeamByInviteId(inviteId, c.Session.UserId) - } else { - c.Err = model.NewAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_invalid.app_error", nil, "", http.StatusBadRequest) - return - } - - if err != nil { - c.Err = err - return - } - - c.App.SanitizeTeam(c.Session, team) - - w.Write([]byte(team.ToJson())) -} - -func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { - - m := model.MapFromJson(r.Body) - name := strings.ToLower(strings.TrimSpace(m["name"])) - - found := c.App.FindTeamByName(name) - - if found { - w.Write([]byte("true")) - } else { - w.Write([]byte("false")) - } -} - -func getTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - teamname := params["team_name"] - - if team, err := c.App.GetTeamByName(teamname); err != nil { - c.Err = err - return - } else { - if (!team.AllowOpenInvite || team.Type != model.TEAM_OPEN) && c.Session.GetTeamByTeamId(team.Id) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - c.App.SanitizeTeam(c.Session, team) - - w.Write([]byte(team.ToJson())) - return - } -} - -func getMyTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { - if len(c.Session.TeamMembers) > 0 { - w.Write([]byte(model.TeamMembersToJson(c.Session.TeamMembers))) - } else { - if members, err := c.App.GetTeamMembersForUser(c.Session.UserId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.TeamMembersToJson(members))) - } - } -} - -func getMyTeamsUnread(c *Context, w http.ResponseWriter, r *http.Request) { - teamId := r.URL.Query().Get("id") - - if unreads, err := c.App.GetTeamsUnreadForUser(teamId, c.Session.UserId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.TeamsUnreadToJson(unreads))) - } -} - -func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { - - team := model.TeamFromJson(r.Body) - if team == nil { - c.SetInvalidParam("updateTeam", "team") - return - } - - team.Id = c.TeamId - - if !c.App.SessionHasPermissionToTeam(c.Session, team.Id, model.PERMISSION_MANAGE_TEAM) { - c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) - return - } - - var err *model.AppError - var updatedTeam *model.Team - - updatedTeam, err = c.App.UpdateTeam(team) - if err != nil { - c.Err = err - return - } - - c.App.SanitizeTeam(c.Session, updatedTeam) - - w.Write([]byte(updatedTeam.ToJson())) -} - -func updateMemberRoles(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateMemberRoles", "user_id") - return - } - - teamId := c.TeamId - - newRoles := props["new_roles"] - if !(model.IsValidUserRoles(newRoles)) { - c.SetInvalidParam("updateMemberRoles", "new_roles") - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, teamId, model.PERMISSION_MANAGE_TEAM_ROLES) { - c.SetPermissionError(model.PERMISSION_MANAGE_TEAM_ROLES) - return - } - - if _, err := c.App.UpdateTeamMemberRoles(teamId, userId, newRoles); err != nil { - c.Err = err - return - } - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { - - if len(c.TeamId) == 0 { - return - } - - if team, err := c.App.GetTeam(c.TeamId); err != nil { - c.Err = err - return - } else if c.HandleEtag(team.Etag(), "Get My Team", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, team.Etag()) - - c.App.SanitizeTeam(c.Session, team) - - w.Write([]byte(team.ToJson())) - return - } -} - -func getTeamStats(c *Context, w http.ResponseWriter, r *http.Request) { - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - stats, err := c.App.GetTeamStats(c.TeamId) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(stats.ToJson())) -} - -func importTeam(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_IMPORT_TEAM) { - c.SetPermissionError(model.PERMISSION_IMPORT_TEAM) - return - } - - if err := r.ParseMultipartForm(10000000); err != nil { - c.Err = model.NewAppError("importTeam", "api.team.import_team.parse.app_error", nil, err.Error(), http.StatusBadRequest) - return - } - - importFromArray, ok := r.MultipartForm.Value["importFrom"] - if !ok || len(importFromArray) < 1 { - c.Err = model.NewAppError("importTeam", "api.team.import_team.no_import_from.app_error", nil, "", http.StatusBadRequest) - return - } - importFrom := importFromArray[0] - - fileSizeStr, ok := r.MultipartForm.Value["filesize"] - if !ok || len(fileSizeStr) < 1 { - c.Err = model.NewAppError("importTeam", "api.team.import_team.unavailable.app_error", nil, "", http.StatusBadRequest) - return - } - - fileSize, err := strconv.ParseInt(fileSizeStr[0], 10, 64) - if err != nil { - c.Err = model.NewAppError("importTeam", "api.team.import_team.integer.app_error", nil, "", http.StatusBadRequest) - return - } - - fileInfoArray, ok := r.MultipartForm.File["file"] - if !ok { - c.Err = model.NewAppError("importTeam", "api.team.import_team.no_file.app_error", nil, "", http.StatusBadRequest) - return - } - - if len(fileInfoArray) <= 0 { - c.Err = model.NewAppError("importTeam", "api.team.import_team.array.app_error", nil, "", http.StatusBadRequest) - return - } - - fileInfo := fileInfoArray[0] - - fileData, err := fileInfo.Open() - if err != nil { - c.Err = model.NewAppError("importTeam", "api.team.import_team.open.app_error", nil, err.Error(), http.StatusBadRequest) - return - } - defer fileData.Close() - - var log *bytes.Buffer - switch importFrom { - case "slack": - var err *model.AppError - if err, log = c.App.SlackImport(fileData, fileSize, c.TeamId); err != nil { - c.Err = err - c.Err.StatusCode = http.StatusBadRequest - } - } - - w.Header().Set("Content-Disposition", "attachment; filename=MattermostImportLog.txt") - w.Header().Set("Content-Type", "application/octet-stream") - if c.Err != nil { - w.WriteHeader(c.Err.StatusCode) - } - io.Copy(w, bytes.NewReader(log.Bytes())) - //http.ServeContent(w, r, "MattermostImportLog.txt", time.Now(), bytes.NewReader(log.Bytes())) -} - -func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) { - m := model.MapFromJson(r.Body) - inviteId := m["invite_id"] - - if team, err := c.App.GetTeamByInviteId(inviteId); err != nil { - c.Err = err - return - } else { - if !(team.Type == model.TEAM_OPEN) { - c.Err = model.NewAppError("getInviteInfo", "api.team.get_invite_info.not_open_team", nil, "id="+inviteId, http.StatusBadRequest) - return - } - - result := map[string]string{} - result["display_name"] = team.DisplayName - result["description"] = team.Description - result["name"] = team.Name - result["id"] = team.Id - w.Write([]byte(model.MapToJson(result))) - } -} - -func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getTeamMembers", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getTeamMembers", "limit") - return - } - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if members, err := c.App.GetTeamMembers(c.TeamId, offset, limit); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.TeamMembersToJson(members))) - return - } -} - -func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - userId := params["user_id"] - if len(userId) < 26 { - c.SetInvalidParam("getTeamMember", "user_id") - return - } - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if member, err := c.App.GetTeamMember(c.TeamId, userId); err != nil { - c.Err = err - return - } else { - w.Write([]byte(member.ToJson())) - return - } -} - -func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) { - userIds := model.ArrayFromJson(r.Body) - if len(userIds) == 0 { - c.SetInvalidParam("getTeamMembersByIds", "user_ids") - return - } - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if members, err := c.App.GetTeamMembersByIds(c.TeamId, userIds); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.TeamMembersToJson(members))) - return - } -} - -func sanitizeTeamMap(c *Context, teams map[string]*model.Team) { - for _, team := range teams { - c.App.SanitizeTeam(c.Session, team) - } -} diff --git a/api/team_test.go b/api/team_test.go deleted file mode 100644 index b7af0f1e8..000000000 --- a/api/team_test.go +++ /dev/null @@ -1,1196 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" -) - -func TestCreateTeam(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, err := Client.CreateTeam(&team) - if err != nil { - t.Fatal(err) - } - - user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(rteam.Data.(*model.Team).Id) - - c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - if len(*c1) != 2 { - t.Fatal("default channels not created") - } - - if rteam.Data.(*model.Team).DisplayName != team.DisplayName { - t.Fatal("full name didn't match") - } - - if _, err := Client.CreateTeam(rteam.Data.(*model.Team)); err == nil { - t.Fatal("Cannot create an existing") - } - - rteam.Data.(*model.Team).Id = "" - if _, err := Client.CreateTeam(rteam.Data.(*model.Team)); err != nil { - if err.Message != "A team with that name already exists" { - t.Fatal(err) - } - } - - if _, err := Client.DoApiPost("/teams/create", "garbage"); err == nil { - t.Fatal("should have been an error") - } -} - -func TestCreateTeamSanitization(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - // Non-admin users can create a team, but they become a team admin by doing so - - t.Run("team admin", func(t *testing.T) { - team := &model.Team{ - DisplayName: t.Name() + "_1", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - } - - if res, err := th.BasicClient.CreateTeam(team); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) - - t.Run("system admin", func(t *testing.T) { - team := &model.Team{ - DisplayName: t.Name() + "_2", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - } - - if res, err := th.SystemAdminClient.CreateTeam(team); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) -} - -func TestAddUserToTeam(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - th.BasicClient.Logout() - - // Test adding a user to a team you are not a member of. - th.SystemAdminClient.SetTeamId(th.BasicTeam.Id) - th.SystemAdminClient.Must(th.SystemAdminClient.RemoveUserFromTeam(th.BasicTeam.Id, th.BasicUser2.Id)) - - th.LoginBasic2() - - user2 := th.CreateUser(th.BasicClient) - - if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user2.Id); err == nil { - t.Fatal("Should have failed because of not being a team member") - } - - // Test adding a user to a team you are a member of. - th.BasicClient.Logout() - th.LoginBasic() - - if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user2.Id); err != nil { - t.Fatal(err) - } - - // Check it worked properly. - if result, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user2.Id); err != nil { - t.Fatal(err) - } else { - rm := result.Data.(map[string]string) - if rm["user_id"] != user2.Id { - t.Fatal("ids didn't match") - } - } - - if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, user2.Id); err != nil { - t.Fatal(err) - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - // Set the config so that only team admins can add a user to a team. - th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) - - // Check that a regular user can't add someone to the team. - user4 := th.CreateUser(th.BasicClient) - if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user4.Id); err == nil { - t.Fatal("should have failed due to permissions error") - } - - // Should work as team admin. - th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) - th.App.InvalidateAllCaches() - - // Change permission level to team user - th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) - - user5 := th.CreateUser(th.BasicClient) - if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user5.Id); err != nil { - t.Fatal(err) - } -} - -func TestRemoveUserFromTeam(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - if _, err := th.BasicClient.RemoveUserFromTeam(th.SystemAdminTeam.Id, th.SystemAdminUser.Id); err == nil { - t.Fatal("should fail not enough permissions") - } else { - if err.Id != "api.context.permissions.app_error" { - t.Fatal("wrong error. Got: " + err.Id) - } - } - - if _, err := th.BasicClient.RemoveUserFromTeam("", th.SystemAdminUser.Id); err == nil { - t.Fatal("should fail not enough permissions") - } else { - if err.Id != "api.context.permissions.app_error" { - t.Fatal("wrong error") - } - } - - if _, err := th.BasicClient.RemoveUserFromTeam("", th.BasicUser.Id); err != nil { - t.Fatal("should have removed the user from the team") - } - - th.BasicClient.Logout() - th.LoginSystemAdmin() - - th.SystemAdminClient.Must(th.SystemAdminClient.AddUserToTeam(th.BasicTeam.Id, th.BasicUser.Id)) - - if _, err := th.SystemAdminClient.RemoveUserFromTeam(th.BasicTeam.Id, th.BasicUser.Id); err != nil { - t.Fatal("should have removed the user from the team") - } -} - -func TestAddUserToTeamFromInvite(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - user2 := th.CreateUser(th.BasicClient) - th.BasicClient.Must(th.BasicClient.Logout()) - th.BasicClient.Must(th.BasicClient.Login(user2.Email, user2.Password)) - - if result, err := th.BasicClient.AddUserToTeamFromInvite("", "", th.BasicTeam.InviteId); err != nil { - t.Fatal(err) - } else { - rtm := result.Data.(*model.Team) - if rtm.Id != th.BasicTeam.Id { - t.Fatal() - } - } -} - -func TestGetAllTeams(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - if r1, err := Client.GetAllTeams(); err != nil { - t.Fatal(err) - } else if teams := r1.Data.(map[string]*model.Team); len(teams) != 1 { - t.Fatal("non admin users only get the teams that they're a member of") - } else if receivedTeam, ok := teams[team.Id]; !ok || receivedTeam.Id != team.Id { - t.Fatal("should've received team that the user is a member of") - } - - if r1, err := th.SystemAdminClient.GetAllTeams(); err != nil { - t.Fatal(err) - } else if teams := r1.Data.(map[string]*model.Team); len(teams) == 1 { - t.Fatal("admin users should receive all teams") - } else if receivedTeam, ok := teams[team.Id]; !ok || receivedTeam.Id != team.Id { - t.Fatal("admin should've received team that they aren't a member of") - } - - Client.Logout() - if _, err := Client.GetAllTeams(); err == nil { - t.Fatal("Should have failed due to not being logged in.") - } -} - -func TestGetAllTeamsSanitization(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - var team *model.Team - if res, err := th.BasicClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_1", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - }); err != nil { - t.Fatal(err) - } else { - team = res.Data.(*model.Team) - } - - var team2 *model.Team - if res, err := th.SystemAdminClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_2", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - }); err != nil { - t.Fatal(err) - } else { - team2 = res.Data.(*model.Team) - } - - t.Run("team admin/team user", func(t *testing.T) { - if res, err := th.BasicClient.GetAllTeams(); err != nil { - t.Fatal(err) - } else { - for _, rteam := range res.Data.(map[string]*model.Team) { - if rteam.Id == team.Id { - if rteam.Email == "" { - t.Fatal("should not have sanitized email for team admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains for team admin") - } - } else if rteam.Id == team2.Id { - if rteam.Email != "" { - t.Fatal("should've sanitized email for non-admin") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains for non-admin") - } - } - } - } - }) - - t.Run("system admin", func(t *testing.T) { - if res, err := th.SystemAdminClient.GetAllTeams(); err != nil { - t.Fatal(err) - } else { - for _, rteam := range res.Data.(map[string]*model.Team) { - if rteam.Id != team.Id && rteam.Id != team2.Id { - continue - } - - if rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - } - } - }) -} - -func TestGetAllTeamListings(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN, AllowOpenInvite: true} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - if r1, err := Client.GetAllTeamListings(); err != nil { - t.Fatal(err) - } else { - teams := r1.Data.(map[string]*model.Team) - if teams[team.Id].Name != team.Name { - t.Fatal("team name doesn't match") - } - } - - th.App.UpdateUserRoles(user.Id, model.SYSTEM_ADMIN_ROLE_ID, false) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - if r1, err := Client.GetAllTeams(); err != nil { - t.Fatal(err) - } else { - teams := r1.Data.(map[string]*model.Team) - if teams[team.Id].Name != team.Name { - t.Fatal("team name doesn't match") - } - } -} - -func TestGetAllTeamListingsSanitization(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - var team *model.Team - if res, err := th.BasicClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_1", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - AllowOpenInvite: true, - }); err != nil { - t.Fatal(err) - } else { - team = res.Data.(*model.Team) - } - - var team2 *model.Team - if res, err := th.SystemAdminClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_2", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - AllowOpenInvite: true, - }); err != nil { - t.Fatal(err) - } else { - team2 = res.Data.(*model.Team) - } - - t.Run("team admin/non-admin", func(t *testing.T) { - if res, err := th.BasicClient.GetAllTeamListings(); err != nil { - t.Fatal(err) - } else { - for _, rteam := range res.Data.(map[string]*model.Team) { - if rteam.Id == team.Id { - if rteam.Email == "" { - t.Fatal("should not have sanitized email for team admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains for team admin") - } - } else if rteam.Id == team2.Id { - if rteam.Email != "" { - t.Fatal("should've sanitized email for non-admin") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains for non-admin") - } - } - } - } - }) - - t.Run("system admin", func(t *testing.T) { - if res, err := th.SystemAdminClient.GetAllTeamListings(); err != nil { - t.Fatal(err) - } else { - for _, rteam := range res.Data.(map[string]*model.Team) { - if rteam.Id != team.Id && rteam.Id != team2.Id { - continue - } - - if rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - } - } - }) -} - -func TestTeamPermDelete(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user1 := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - th.LinkUserToTeam(user1, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user1.Id)) - - Client.Login(user1.Email, "passwd1") - Client.SetTeamId(team.Id) - - channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - post1 := &model.Post{ChannelId: channel1.Id, Message: "search for post1"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - post2 := &model.Post{ChannelId: channel1.Id, Message: "search for post2"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - post3 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag search for post3"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - post4 := &model.Post{ChannelId: channel1.Id, Message: "hashtag for post4"} - post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) - - c := &Context{} - c.RequestId = model.NewId() - c.IpAddress = "test" - - err := th.App.PermanentDeleteTeam(team) - if err != nil { - t.Fatal(err) - } - - Client.ClearOAuthToken() -} - -func TestInviteMembers(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - invite := make(map[string]string) - invite["email"] = "success+" + model.NewId() + "@simulator.amazonses.com" - invite["first_name"] = "Test" - invite["last_name"] = "Guy" - invites := &model.Invites{Invites: []map[string]string{invite}} - invites.Invites = append(invites.Invites, invite) - - if _, err := Client.InviteMembers(invites); err != nil { - t.Fatal(err) - } - - invites2 := &model.Invites{Invites: []map[string]string{}} - if _, err := Client.InviteMembers(invites2); err == nil { - t.Fatal("Should have errored out on no invites to send") - } - - // Check the appropriate permissions are enforced. - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - - // Set the config so that only team admins can add a user to a team. - th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) - - th.LoginBasic2() - th.LinkUserToTeam(th.BasicUser2, team) - - if _, err := Client.InviteMembers(invites); err == nil { - t.Fatal("should have errored not team admin and licensed") - } - - th.UpdateUserToTeamAdmin(th.BasicUser2, team) - Client.Logout() - th.LoginBasic2() - Client.SetTeamId(team.Id) - - if _, err := Client.InviteMembers(invites); err != nil { - t.Fatal(err) - } -} - -func TestUpdateTeamDisplayName(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - Client.Login(user2.Email, "passwd1") - Client.SetTeamId(team.Id) - - vteam := &model.Team{DisplayName: team.DisplayName, Name: team.Name, Email: team.Email, Type: team.Type} - vteam.DisplayName = "NewName" - if _, err := Client.UpdateTeam(vteam); err == nil { - t.Fatal("Should have errored, not admin") - } - - th.LoginBasic() - - vteam.DisplayName = "" - if _, err := Client.UpdateTeam(vteam); err == nil { - t.Fatal("Should have errored, empty name") - } - - vteam.DisplayName = "NewName" - if _, err := Client.UpdateTeam(vteam); err != nil { - t.Fatal(err) - } -} - -func TestUpdateTeamSanitization(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - var team *model.Team - if res, err := th.BasicClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_1", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - }); err != nil { - t.Fatal(err) - } else { - team = res.Data.(*model.Team) - } - - // Non-admin users cannot update the team - - t.Run("team admin", func(t *testing.T) { - // API v3 always assumes you're updating the current team - th.BasicClient.SetTeamId(team.Id) - - if res, err := th.BasicClient.UpdateTeam(team); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email for admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) - - t.Run("system admin", func(t *testing.T) { - // API v3 always assumes you're updating the current team - th.SystemAdminClient.SetTeamId(team.Id) - - if res, err := th.SystemAdminClient.UpdateTeam(team); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email for admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) -} - -func TestFuzzyTeamCreate(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - for i := 0; i < len(utils.FUZZY_STRINGS_NAMES) || i < len(utils.FUZZY_STRINGS_EMAILS); i++ { - testDisplayName := "Name" - testEmail := "test@nowhere.com" - - if i < len(utils.FUZZY_STRINGS_NAMES) { - testDisplayName = utils.FUZZY_STRINGS_NAMES[i] - } - if i < len(utils.FUZZY_STRINGS_EMAILS) { - testEmail = utils.FUZZY_STRINGS_EMAILS[i] - } - - team := model.Team{DisplayName: testDisplayName, Name: "z-z-" + model.NewId() + "a", Email: testEmail, Type: model.TEAM_OPEN} - - _, err := Client.CreateTeam(&team) - if err != nil { - t.Fatal(err) - } - } -} - -func TestGetMyTeam(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(team) - team = rteam.Data.(*model.Team) - - Client.Logout() - - user := model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - Client.Login(user.Email, user.Password) - Client.SetTeamId(team.Id) - - if result, err := Client.GetMyTeam(""); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.Team).DisplayName != team.DisplayName { - t.Fatal("team names did not match") - } - if result.Data.(*model.Team).Name != team.Name { - t.Fatal("team domains did not match") - } - if result.Data.(*model.Team).Type != team.Type { - t.Fatal("team types did not match") - } - } -} - -func TestGetMyTeamSanitization(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - var team *model.Team - if res, err := th.BasicClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_1", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - }); err != nil { - t.Fatal(err) - } else { - team = res.Data.(*model.Team) - } - - t.Run("team user", func(t *testing.T) { - th.LinkUserToTeam(th.BasicUser2, team) - - client := th.CreateClient() - client.Must(client.Login(th.BasicUser2.Email, th.BasicUser2.Password)) - - client.SetTeamId(team.Id) - - if res, err := client.GetMyTeam(""); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email != "" { - t.Fatal("should've sanitized email") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains") - } - }) - - t.Run("team admin", func(t *testing.T) { - th.BasicClient.SetTeamId(team.Id) - - if res, err := th.BasicClient.GetMyTeam(""); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) - - t.Run("system admin", func(t *testing.T) { - th.SystemAdminClient.SetTeamId(team.Id) - - if res, err := th.SystemAdminClient.GetMyTeam(""); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) -} - -func TestGetTeamMembers(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if result, err := th.BasicClient.GetTeamMembers(th.BasicTeam.Id, 0, 100); err != nil { - t.Fatal(err) - } else { - members := result.Data.([]*model.TeamMember) - if len(members) == 0 { - t.Fatal("should have results") - } - } - - if _, err := th.BasicClient.GetTeamMembers("junk", 0, 100); err == nil { - t.Fatal("should have errored - bad team id") - } -} - -func TestGetMyTeamMembers(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if result, err := th.BasicClient.GetMyTeamMembers(); err != nil { - t.Fatal(err) - } else { - members := result.Data.([]*model.TeamMember) - if len(members) == 0 { - t.Fatal("should have results") - } - } -} - -func TestGetMyTeamsUnread(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if result, err := th.BasicClient.GetMyTeamsUnread(""); err != nil { - t.Fatal(err) - } else { - members := result.Data.([]*model.TeamUnread) - if len(members) == 0 { - t.Fatal("should have results") - } - } - - if result, err := th.BasicClient.GetMyTeamsUnread(th.BasicTeam.Id); err != nil { - t.Fatal(err) - } else { - members := result.Data.([]*model.TeamUnread) - if len(members) != 0 { - t.Fatal("should not have results") - } - } -} - -func TestGetTeamMember(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if result, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, th.BasicUser.Id); err != nil { - t.Fatal(err) - } else { - member := result.Data.(*model.TeamMember) - if member == nil { - t.Fatal("should be valid") - } - } - - if _, err := th.BasicClient.GetTeamMember("junk", th.BasicUser.Id); err == nil { - t.Fatal("should have errored - bad team id") - } - - if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, ""); err == nil { - t.Fatal("should have errored - blank user id") - } - - if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, "junk"); err == nil { - t.Fatal("should have errored - bad user id") - } - - if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, "12345678901234567890123456"); err == nil { - t.Fatal("should have errored - bad user id") - } -} - -func TestGetTeamMembersByIds(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if result, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{th.BasicUser.Id}); err != nil { - t.Fatal(err) - } else { - member := result.Data.([]*model.TeamMember)[0] - if member.UserId != th.BasicUser.Id { - t.Fatal("user id did not match") - } - if member.TeamId != th.BasicTeam.Id { - t.Fatal("team id did not match") - } - } - - if result, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{th.BasicUser.Id, th.BasicUser2.Id, model.NewId()}); err != nil { - t.Fatal(err) - } else { - members := result.Data.([]*model.TeamMember) - if len(members) != 2 { - t.Fatal("length should have been 2") - } - } - - if _, err := th.BasicClient.GetTeamMembersByIds("junk", []string{th.BasicUser.Id}); err == nil { - t.Fatal("should have errored - bad team id") - } - - if _, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{}); err == nil { - t.Fatal("should have errored - empty user ids") - } -} - -func TestUpdateTeamMemberRoles(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - th.SystemAdminClient.SetTeamId(th.BasicTeam.Id) - th.LinkUserToTeam(th.SystemAdminUser, th.BasicTeam) - - const BASIC_MEMBER = "team_user" - const TEAM_ADMIN = "team_user team_admin" - - // user 1 trying to promote user 2 - if _, err := th.BasicClient.UpdateTeamRoles(th.BasicUser2.Id, TEAM_ADMIN); err == nil { - t.Fatal("Should have errored, not team admin") - } - - // user 1 trying to promote themselves - if _, err := th.BasicClient.UpdateTeamRoles(th.BasicUser.Id, TEAM_ADMIN); err == nil { - t.Fatal("Should have errored, not team admin") - } - - // user 1 trying to demote someone - if _, err := th.BasicClient.UpdateTeamRoles(th.SystemAdminUser.Id, BASIC_MEMBER); err == nil { - t.Fatal("Should have errored, not team admin") - } - - // system admin promoting user1 - if _, err := th.SystemAdminClient.UpdateTeamRoles(th.BasicUser.Id, TEAM_ADMIN); err != nil { - t.Fatal("Should have worked: " + err.Error()) - } - - // user 1 trying to promote user 2 - if _, err := th.BasicClient.UpdateTeamRoles(th.BasicUser2.Id, TEAM_ADMIN); err != nil { - t.Fatal("Should have worked, user is team admin: " + th.BasicUser.Id) - } - - // user 1 trying to demote user 2 - if _, err := th.BasicClient.UpdateTeamRoles(th.BasicUser2.Id, BASIC_MEMBER); err != nil { - t.Fatal("Should have worked, user is team admin") - } - - // user 1 trying to demote a system admin - if _, err := th.BasicClient.UpdateTeamRoles(th.SystemAdminUser.Id, BASIC_MEMBER); err != nil { - t.Fatal("Should have worked, user is team admin and has the ability to manage permissions on this team.") - // Note to anyone who thinks this test is wrong: - // This operation will not effect the system admin's permissions because they have global access to all teams. - // Their team level permissions are irrelevant. A team admin should be able to manage team level permissions. - } - - // System admins should be able to manipulate permission no matter what their team level permissions are. - // systemAdmin trying to promote user 2 - if _, err := th.SystemAdminClient.UpdateTeamRoles(th.BasicUser2.Id, TEAM_ADMIN); err != nil { - t.Fatal("Should have worked, user is system admin") - } - - // system admin trying to demote user 2 - if _, err := th.SystemAdminClient.UpdateTeamRoles(th.BasicUser2.Id, BASIC_MEMBER); err != nil { - t.Fatal("Should have worked, user is system admin") - } - - // user 1 trying to demote himself - if _, err := th.BasicClient.UpdateTeamRoles(th.BasicUser.Id, BASIC_MEMBER); err != nil { - t.Fatal("Should have worked, user is team admin") - } -} - -func TestGetTeamStats(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.TeamStats).TotalMemberCount != 2 { - t.Fatal("wrong count") - } - - if result.Data.(*model.TeamStats).ActiveMemberCount != 2 { - t.Fatal("wrong count") - } - } - - th.SystemAdminClient.Must(th.SystemAdminClient.UpdateActive(th.BasicUser2.Id, false)) - - if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.TeamStats).TotalMemberCount != 2 { - t.Fatal("wrong count") - } - - if result.Data.(*model.TeamStats).ActiveMemberCount != 1 { - t.Fatal("wrong count") - } - } - - if _, err := th.SystemAdminClient.GetTeamStats("junk"); err == nil { - t.Fatal("should fail invalid teamid") - } else { - if err.Id != "store.sql_team.get.find.app_error" { - t.Fatal("wrong error. Got: " + err.Id) - } - } - - if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.TeamStats).TotalMemberCount != 2 { - t.Fatal("wrong count") - } - } - - user := model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - Client.Login(user.Email, user.Password) - - if _, err := Client.GetTeamStats(th.BasicTeam.Id); err == nil { - t.Fatal("should have errored - not on team") - } -} - -func TestUpdateTeamDescription(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Jabba the Hutt", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - Client.Login(user2.Email, "passwd1") - Client.SetTeamId(team.Id) - - vteam := &model.Team{DisplayName: team.DisplayName, Name: team.Name, Description: team.Description, Email: team.Email, Type: team.Type} - vteam.Description = "yommamma" - if _, err := Client.UpdateTeam(vteam); err == nil { - t.Fatal("Should have errored, not admin") - } - - th.LoginBasic() - - vteam.Description = "" - if _, err := Client.UpdateTeam(vteam); err != nil { - t.Fatal("Should have errored, should save blank Description") - } - - vteam.Description = "yommamma" - if _, err := Client.UpdateTeam(vteam); err != nil { - t.Fatal(err) - } -} - -func TestGetTeamByName(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TEAM_OPEN, AllowOpenInvite: false} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TEAM_OPEN, AllowOpenInvite: true} - team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - - team3 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+" + model.NewId() + "@simulator.amazonses.com", Type: model.TEAM_INVITE, AllowOpenInvite: true} - team3 = Client.Must(Client.CreateTeam(team3)).Data.(*model.Team) - - if _, err := Client.GetTeamByName(team.Name); err != nil { - t.Fatal("Failed to get team") - } - - if _, err := Client.GetTeamByName("InvalidTeamName"); err == nil { - t.Fatal("Should not exist this team") - } - - if _, err := Client.GetTeamByName(team2.Name); err != nil { - t.Fatal("Failed to get team") - } - - Client.Must(Client.Logout()) - - user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Jabba the Hutt", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - Client.Login(user2.Email, "passwd1") - - // AllowInviteOpen is false and team is open and user is not part of the team - if _, err := Client.GetTeamByName(team.Name); err == nil { - t.Fatal("Should fail dont have permissions to get the team") - } - - if _, err := Client.GetTeamByName("InvalidTeamName"); err == nil { - t.Fatal("Should not exist this team") - } - - // AllowInviteOpen is true and is open and user is not part of the team - if _, err := Client.GetTeamByName(team2.Name); err != nil { - t.Fatal("Should not fail team is open") - } - - // AllowInviteOpen is true and is invite only and user is not part of the team - if _, err := Client.GetTeamByName(team3.Name); err == nil { - t.Fatal("Should fail team is invite only") - } - - Client.Must(Client.Logout()) - th.BasicClient.Logout() - th.LoginSystemAdmin() - - if _, err := th.SystemAdminClient.GetTeamByName(team.Name); err != nil { - t.Fatal("Should not fail to get team the user is admin") - } - - if _, err := th.SystemAdminClient.GetTeamByName(team2.Name); err != nil { - t.Fatal("Should not fail to get team the user is admin and team is open") - } - - if _, err := th.SystemAdminClient.GetTeamByName(team3.Name); err != nil { - t.Fatal("Should not fail to get team the user is admin and team is invite") - } - - if _, err := Client.GetTeamByName("InvalidTeamName"); err == nil { - t.Fatal("Should not exist this team") - } - - Client.Logout() - if _, err := Client.GetTeamByName(th.BasicTeam.Name); err == nil { - t.Fatal("Should have failed when not logged in.") - } -} - -func TestGetTeamByNameSanitization(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - var team *model.Team - if res, err := th.BasicClient.CreateTeam(&model.Team{ - DisplayName: t.Name() + "_1", - Name: GenerateTestTeamName(), - Email: th.GenerateTestEmail(), - Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", - }); err != nil { - t.Fatal(err) - } else { - team = res.Data.(*model.Team) - } - - t.Run("team user", func(t *testing.T) { - th.LinkUserToTeam(th.BasicUser2, team) - - client := th.CreateClient() - client.Must(client.Login(th.BasicUser2.Email, th.BasicUser2.Password)) - - if res, err := client.GetTeamByName(team.Name); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email != "" { - t.Fatal("should've sanitized email") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains") - } - }) - - t.Run("team admin", func(t *testing.T) { - if res, err := th.BasicClient.GetTeamByName(team.Name); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) - - t.Run("system admin", func(t *testing.T) { - th.SystemAdminClient.SetTeamId(team.Id) - - if res, err := th.SystemAdminClient.GetTeamByName(team.Name); err != nil { - t.Fatal(err) - } else if rteam := res.Data.(*model.Team); rteam.Email == "" { - t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") - } - }) -} - -func TestFindTeamByName(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - Client.Logout() - - if _, err := Client.FindTeamByName(th.BasicTeam.Name); err == nil { - t.Fatal("Should have failed when not logged in.") - } -} diff --git a/api/user.go b/api/user.go deleted file mode 100644 index 7592d1119..000000000 --- a/api/user.go +++ /dev/null @@ -1,1245 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "net/http" - "strconv" - "time" - - "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" -) - -func (api *API) InitUser() { - api.BaseRoutes.Users.Handle("/create", api.ApiAppHandler(createUser)).Methods("POST") - api.BaseRoutes.Users.Handle("/update", api.ApiUserRequired(updateUser)).Methods("POST") - api.BaseRoutes.Users.Handle("/update_active", api.ApiUserRequired(updateActive)).Methods("POST") - api.BaseRoutes.Users.Handle("/update_notify", api.ApiUserRequired(updateUserNotify)).Methods("POST") - api.BaseRoutes.Users.Handle("/newpassword", api.ApiUserRequired(updatePassword)).Methods("POST") - api.BaseRoutes.Users.Handle("/send_password_reset", api.ApiAppHandler(sendPasswordReset)).Methods("POST") - api.BaseRoutes.Users.Handle("/reset_password", api.ApiAppHandler(resetPassword)).Methods("POST") - api.BaseRoutes.Users.Handle("/login", api.ApiAppHandler(login)).Methods("POST") - api.BaseRoutes.Users.Handle("/logout", api.ApiAppHandler(logout)).Methods("POST") - api.BaseRoutes.Users.Handle("/revoke_session", api.ApiUserRequired(revokeSession)).Methods("POST") - api.BaseRoutes.Users.Handle("/attach_device", api.ApiUserRequired(attachDeviceId)).Methods("POST") - //DEPRICATED FOR SECURITY USE APIV4 api.BaseRoutes.Users.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST") - //DEPRICATED FOR SECURITY USE APIV4 api.BaseRoutes.Users.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST") - api.BaseRoutes.Users.Handle("/newimage", api.ApiUserRequired(uploadProfileImage)).Methods("POST") - api.BaseRoutes.Users.Handle("/me", api.ApiUserRequired(getMe)).Methods("GET") - api.BaseRoutes.Users.Handle("/initial_load", api.ApiAppHandler(getInitialLoad)).Methods("GET") - api.BaseRoutes.Users.Handle("/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getProfiles)).Methods("GET") - api.BaseRoutes.NeedTeam.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getProfilesInTeam)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getProfilesInChannel)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/users/not_in_channel/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getProfilesNotInChannel)).Methods("GET") - api.BaseRoutes.Users.Handle("/search", api.ApiUserRequired(searchUsers)).Methods("POST") - api.BaseRoutes.Users.Handle("/ids", api.ApiUserRequired(getProfilesByIds)).Methods("POST") - api.BaseRoutes.Users.Handle("/autocomplete", api.ApiUserRequired(autocompleteUsers)).Methods("GET") - - api.BaseRoutes.NeedTeam.Handle("/users/autocomplete", api.ApiUserRequired(autocompleteUsersInTeam)).Methods("GET") - api.BaseRoutes.NeedChannel.Handle("/users/autocomplete", api.ApiUserRequired(autocompleteUsersInChannel)).Methods("GET") - - api.BaseRoutes.Users.Handle("/mfa", api.ApiAppHandler(checkMfa)).Methods("POST") - api.BaseRoutes.Users.Handle("/generate_mfa_secret", api.ApiUserRequiredMfa(generateMfaSecret)).Methods("GET") - api.BaseRoutes.Users.Handle("/update_mfa", api.ApiUserRequiredMfa(updateMfa)).Methods("POST") - - api.BaseRoutes.Users.Handle("/claim/email_to_oauth", api.ApiAppHandler(emailToOAuth)).Methods("POST") - api.BaseRoutes.Users.Handle("/claim/oauth_to_email", api.ApiUserRequired(oauthToEmail)).Methods("POST") - api.BaseRoutes.Users.Handle("/claim/email_to_ldap", api.ApiAppHandler(emailToLdap)).Methods("POST") - api.BaseRoutes.Users.Handle("/claim/ldap_to_email", api.ApiAppHandler(ldapToEmail)).Methods("POST") - - api.BaseRoutes.NeedUser.Handle("/get", api.ApiUserRequired(getUser)).Methods("GET") - api.BaseRoutes.Users.Handle("/name/{username:[A-Za-z0-9_\\-.]+}", api.ApiUserRequired(getByUsername)).Methods("GET") - api.BaseRoutes.Users.Handle("/email/{email}", api.ApiUserRequired(getByEmail)).Methods("GET") - api.BaseRoutes.NeedUser.Handle("/sessions", api.ApiUserRequired(getSessions)).Methods("GET") - api.BaseRoutes.NeedUser.Handle("/audits", api.ApiUserRequired(getAudits)).Methods("GET") - api.BaseRoutes.NeedUser.Handle("/image", api.ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET") - api.BaseRoutes.NeedUser.Handle("/update_roles", api.ApiUserRequired(updateRoles)).Methods("POST") -} - -func createUser(c *Context, w http.ResponseWriter, r *http.Request) { - user := model.UserFromJson(r.Body) - - if user == nil { - c.SetInvalidParam("createUser", "user") - return - } - - tokenId := r.URL.Query().Get("t") - inviteId := r.URL.Query().Get("iid") - - var ruser *model.User - var err *model.AppError - if len(tokenId) > 0 { - ruser, err = c.App.CreateUserWithToken(user, tokenId) - } else if len(inviteId) > 0 { - ruser, err = c.App.CreateUserWithInviteId(user, inviteId) - } else { - ruser, err = c.App.CreateUserFromSignup(user) - } - - if err != nil { - c.Err = err - return - } - - w.Write([]byte(ruser.ToJson())) -} - -func login(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - loginId := props["login_id"] - password := props["password"] - mfaToken := props["token"] - deviceId := props["device_id"] - ldapOnly := props["ldap_only"] == "true" - - c.LogAudit("attempt - user_id=" + id + " login_id=" + loginId) - user, err := c.App.AuthenticateUserForLogin(id, loginId, password, mfaToken, ldapOnly) - if err != nil { - c.LogAudit("failure - user_id=" + id + " login_id=" + loginId) - c.Err = err - return - } - - c.LogAuditWithUserId(user.Id, "success") - - doLogin(c, w, r, user, deviceId) - if c.Err != nil { - return - } - - user.Sanitize(map[string]bool{}) - - w.Write([]byte(user.ToJson())) -} - -// User MUST be authenticated completely before calling Login -func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { - session, err := c.App.DoLogin(w, r, user, deviceId) - if err != nil { - c.Err = err - return - } - - c.Session = *session -} - -func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - id := props["id"] - - if err := c.App.RevokeSessionById(id); err != nil { - c.Err = err - return - } - - w.Write([]byte(model.MapToJson(props))) -} - -func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - deviceId := props["device_id"] - if len(deviceId) == 0 { - c.SetInvalidParam("attachDevice", "deviceId") - return - } - - // A special case where we logout of all other sessions with the same device id - if err := c.App.RevokeSessionsForDeviceId(c.Session.UserId, deviceId, c.Session.Id); err != nil { - c.Err = err - c.Err.StatusCode = http.StatusInternalServerError - return - } - - c.App.ClearSessionCacheForUser(c.Session.UserId) - c.Session.SetExpireInDays(*c.App.Config().ServiceSettings.SessionLengthMobileInDays) - - maxAge := *c.App.Config().ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24 - - secure := false - if app.GetProtocol(r) == "https" { - secure = true - } - - expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0) - sessionCookie := &http.Cookie{ - Name: model.SESSION_COOKIE_TOKEN, - Value: c.Session.Token, - Path: "/", - MaxAge: maxAge, - Expires: expiresAt, - HttpOnly: true, - Secure: secure, - } - - http.SetCookie(w, sessionCookie) - - if err := c.App.AttachDeviceId(c.Session.Id, deviceId, c.Session.ExpiresAt); err != nil { - c.Err = err - return - } - - w.Write([]byte(model.MapToJson(props))) -} - -func getSessions(c *Context, w http.ResponseWriter, r *http.Request) { - - params := mux.Vars(r) - id := params["user_id"] - - if !c.App.SessionHasPermissionToUser(c.Session, id) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) - return - } - - if sessions, err := c.App.GetSessions(id); err != nil { - c.Err = err - return - } else { - for _, session := range sessions { - session.Sanitize() - } - - w.Write([]byte(model.SessionsToJson(sessions))) - } -} - -func logout(c *Context, w http.ResponseWriter, r *http.Request) { - data := make(map[string]string) - data["user_id"] = c.Session.UserId - - Logout(c, w, r) - if c.Err == nil { - w.Write([]byte(model.MapToJson(data))) - } -} - -func Logout(c *Context, w http.ResponseWriter, r *http.Request) { - c.LogAudit("") - c.RemoveSessionCookie(w, r) - if c.Session.Id != "" { - if err := c.App.RevokeSessionById(c.Session.Id); err != nil { - c.Err = err - return - } - } -} - -func getMe(c *Context, w http.ResponseWriter, r *http.Request) { - - if user, err := c.App.GetUser(c.Session.UserId); err != nil { - c.Err = err - c.RemoveSessionCookie(w, r) - mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", c.Session.UserId), mlog.String("user_id", c.Session.UserId)) - return - } else if c.HandleEtag(user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress), "Get Me", w, r) { - return - } else { - user.Sanitize(map[string]bool{}) - w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress)) - w.Write([]byte(user.ToJson())) - return - } -} - -func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) { - - il := model.InitialLoad{} - - if len(c.Session.UserId) != 0 { - var err *model.AppError - - il.User, err = c.App.GetUser(c.Session.UserId) - if err != nil { - c.Err = err - return - } - il.User.Sanitize(map[string]bool{}) - - il.Preferences, err = c.App.GetPreferencesForUser(c.Session.UserId) - if err != nil { - c.Err = err - return - } - - il.Teams, err = c.App.GetTeamsForUser(c.Session.UserId) - if err != nil { - c.Err = err - return - } - - for _, team := range il.Teams { - team.Sanitize() - } - - il.TeamMembers = c.Session.TeamMembers - } - - if c.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 = c.App.IsFirstUserAccount() - } - - il.ClientCfg = c.App.ClientConfig() - if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - il.LicenseCfg = c.App.ClientLicense() - } else { - il.LicenseCfg = c.App.GetSanitizedClientLicense() - } - - w.Write([]byte(il.ToJson())) -} - -func getUser(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["user_id"] - - var user *model.User - var err *model.AppError - - if user, err = c.App.GetUser(id); err != nil { - c.Err = err - return - } - - etag := user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress) - - if c.HandleEtag(etag, "Get User", w, r) { - return - } else { - c.App.SanitizeProfile(user, c.IsSystemAdmin()) - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Write([]byte(user.ToJson())) - return - } -} - -func getByUsername(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - username := params["username"] - - var user *model.User - var err *model.AppError - - if user, err = c.App.GetUserByUsername(username); err != nil { - c.Err = err - return - } else if c.HandleEtag(user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress), "Get By Username", w, r) { - return - } else { - sanitizeProfile(c, user) - - w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress)) - w.Write([]byte(user.ToJson())) - return - } -} - -func getByEmail(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - email := params["email"] - - if user, err := c.App.GetUserByEmail(email); err != nil { - c.Err = err - return - } else if c.HandleEtag(user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress), "Get By Email", w, r) { - return - } else { - sanitizeProfile(c, user) - - w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(c.App.Config().PrivacySettings.ShowFullName, c.App.Config().PrivacySettings.ShowEmailAddress)) - w.Write([]byte(user.ToJson())) - return - } -} - -func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getProfiles", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getProfiles", "limit") - return - } - - etag := c.App.GetUsersEtag() + params["offset"] + "." + params["limit"] - if c.HandleEtag(etag, "Get Profiles", w, r) { - return - } - - if profiles, err := c.App.GetUsersMap(offset, limit, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Write([]byte(model.UserMapToJson(profiles))) - } -} - -func getProfilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - teamId := params["team_id"] - - if c.Session.GetTeamByTeamId(teamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - return - } - } - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getProfilesInTeam", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getProfilesInTeam", "limit") - return - } - - etag := c.App.GetUsersInTeamEtag(teamId) - if c.HandleEtag(etag, "Get Profiles In Team", w, r) { - return - } - - if profiles, err := c.App.GetUsersInTeamMap(teamId, offset, limit, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Write([]byte(model.UserMapToJson(profiles))) - } -} - -func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getProfiles", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getProfiles", "limit") - return - } - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if profiles, err := c.App.GetUsersInChannelMap(channelId, offset, limit, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.UserMapToJson(profiles))) - } -} - -func getProfilesNotInChannel(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - - if c.Session.GetTeamByTeamId(c.TeamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) - return - } - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - offset, err := strconv.Atoi(params["offset"]) - if err != nil { - c.SetInvalidParam("getProfiles", "offset") - return - } - - limit, err := strconv.Atoi(params["limit"]) - if err != nil { - c.SetInvalidParam("getProfiles", "limit") - return - } - - if profiles, err := c.App.GetUsersNotInChannelMap(c.TeamId, channelId, offset, limit, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.UserMapToJson(profiles))) - } -} - -func getAudits(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["user_id"] - - if !c.App.SessionHasPermissionToUser(c.Session, id) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) - return - } - - if audits, err := c.App.GetAudits(id, 20); err != nil { - c.Err = err - return - } else { - etag := audits.Etag() - - if c.HandleEtag(etag, "Get Audits", w, r) { - return - } - - if len(etag) > 0 { - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - } - - w.Write([]byte(audits.ToJson())) - return - } -} - -func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - id := params["user_id"] - readFailed := false - - var etag string - - if users, err := c.App.GetUsersByIds([]string{id}, false); err != nil { - c.Err = err - return - } else { - if len(users) == 0 { - c.Err = model.NewAppError("getProfileImage", "store.sql_user.get_profiles.app_error", nil, "", http.StatusInternalServerError) - return - } - - user := users[0] - etag = strconv.FormatInt(user.LastPictureUpdate, 10) - if c.HandleEtag(etag, "Profile Image", w, r) { - return - } - - var img []byte - img, readFailed, err = c.App.GetProfileImage(user) - if err != nil { - c.Err = err - return - } - - if readFailed { - w.Header().Set("Cache-Control", "max-age=300, public") // 5 mins - } else { - w.Header().Set("Cache-Control", "max-age=86400, public") // 24 hrs - w.Header().Set(model.HEADER_ETAG_SERVER, etag) - } - - w.Header().Set("Content-Type", "image/png") - w.Write(img) - } -} - -func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { - if len(*c.App.Config().FileSettings.DriverName) == 0 { - c.Err = model.NewAppError("uploadProfileImage", "api.user.upload_profile_user.storage.app_error", nil, "", http.StatusNotImplemented) - return - } - - if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize { - c.Err = model.NewAppError("uploadProfileImage", "api.user.upload_profile_user.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge) - return - } - - if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil { - c.Err = model.NewAppError("uploadProfileImage", "api.user.upload_profile_user.parse.app_error", nil, "", http.StatusBadRequest) - return - } - - m := r.MultipartForm - - imageArray, ok := m.File["image"] - if !ok { - c.Err = model.NewAppError("uploadProfileImage", "api.user.upload_profile_user.no_file.app_error", nil, "", http.StatusBadRequest) - return - } - - if len(imageArray) <= 0 { - c.Err = model.NewAppError("uploadProfileImage", "api.user.upload_profile_user.array.app_error", nil, "", http.StatusBadRequest) - return - } - - imageData := imageArray[0] - - if err := c.App.SetProfileImage(c.Session.UserId, imageData); err != nil { - c.Err = err - return - } - - c.LogAudit("") - - // write something as the response since jQuery expects a json response - w.Write([]byte("true")) -} - -func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { - user := model.UserFromJson(r.Body) - - if user == nil { - c.SetInvalidParam("updateUser", "user") - return - } - - if !c.App.SessionHasPermissionToUser(c.Session, user.Id) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) - return - } - - if ruser, err := c.App.UpdateUserAsUser(user, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - c.LogAudit("") - w.Write([]byte(ruser.ToJson())) - } -} - -func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { - c.LogAudit("attempted") - - props := model.MapFromJson(r.Body) - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updatePassword", "user_id") - return - } - - currentPassword := props["current_password"] - if len(currentPassword) <= 0 { - c.SetInvalidParam("updatePassword", "current_password") - return - } - - newPassword := props["new_password"] - - if userId != c.Session.UserId { - c.Err = model.NewAppError("updatePassword", "api.user.update_password.context.app_error", nil, "", http.StatusForbidden) - return - } - - if err := c.App.UpdatePasswordAsUser(userId, currentPassword, newPassword); err != nil { - c.LogAudit("failed") - c.Err = err - return - } else { - c.LogAudit("completed") - - data := make(map[string]string) - data["user_id"] = c.Session.UserId - w.Write([]byte(model.MapToJson(data))) - } -} - -func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - params := mux.Vars(r) - - userId := params["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateMemberRoles", "user_id") - return - } - - newRoles := props["new_roles"] - if !(model.IsValidUserRoles(newRoles)) { - c.SetInvalidParam("updateMemberRoles", "new_roles") - return - } - - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_ROLES) { - c.SetPermissionError(model.PERMISSION_MANAGE_ROLES) - return - } - - if _, err := c.App.UpdateUserRoles(userId, newRoles, true); err != nil { - return - } else { - c.LogAuditWithUserId(userId, "roles="+newRoles) - } - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateActive", "user_id") - return - } - - active := props["active"] == "true" - - // true when you're trying to de-activate yourself - isSelfDeactive := !active && userId == c.Session.UserId - - if !isSelfDeactive && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.Err = model.NewAppError("updateActive", "api.user.update_active.permissions.app_error", nil, "userId="+userId, http.StatusForbidden) - return - } - - var ruser *model.User - var err *model.AppError - - if ruser, err = c.App.GetUser(userId); err != nil { - c.Err = err - return - } - - if _, err := c.App.UpdateActive(ruser, active); err != nil { - c.Err = err - } else { - c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active)) - w.Write([]byte(ruser.ToJson())) - } -} - -func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - email := props["email"] - if len(email) == 0 { - c.SetInvalidParam("sendPasswordReset", "email") - return - } - - if sent, err := c.App.SendPasswordReset(email, c.App.GetSiteURL()); err != nil { - c.Err = err - return - } else if sent { - c.LogAudit("sent=" + email) - } - - w.Write([]byte(model.MapToJson(props))) -} - -func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - code := props["code"] - if len(code) != model.TOKEN_SIZE { - c.SetInvalidParam("resetPassword", "code") - return - } - - newPassword := props["new_password"] - - c.LogAudit("attempt - token=" + code) - - if err := c.App.ResetPasswordFromToken(code, newPassword); err != nil { - c.LogAudit("fail - token=" + code) - c.Err = err - return - } - - c.LogAudit("success - token=" + code) - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - userId := props["user_id"] - if len(userId) != 26 { - c.SetInvalidParam("updateUserNotify", "user_id") - return - } - - if !c.App.SessionHasPermissionToUser(c.Session, userId) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) - return - } - - delete(props, "user_id") - - email := props["email"] - if len(email) == 0 { - c.SetInvalidParam("updateUserNotify", "email") - return - } - - desktop_sound := props["desktop_sound"] - if len(desktop_sound) == 0 { - c.SetInvalidParam("updateUserNotify", "desktop_sound") - return - } - - desktop := props["desktop"] - if len(desktop) == 0 { - c.SetInvalidParam("updateUserNotify", "desktop") - return - } - - comments := props["comments"] - if len(comments) == 0 { - c.SetInvalidParam("updateUserNotify", "comments") - return - } - - ruser, err := c.App.UpdateUserNotifyProps(userId, props) - if err != nil { - c.Err = err - return - } - - c.LogAuditWithUserId(ruser.Id, "") - - options := c.App.Config().GetSanitizeOptions() - options["passwordupdate"] = false - ruser.Sanitize(options) - w.Write([]byte(ruser.ToJson())) -} - -func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - password := props["password"] - if len(password) == 0 { - c.SetInvalidParam("emailToOAuth", "password") - return - } - - mfaToken := props["token"] - - service := props["service"] - if len(service) == 0 { - c.SetInvalidParam("emailToOAuth", "service") - return - } - - email := props["email"] - if len(email) == 0 { - c.SetInvalidParam("emailToOAuth", "email") - return - } - - link, err := c.App.SwitchEmailToOAuth(w, r, email, password, mfaToken, service) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success for email=" + email) - w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) -} - -func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - password := props["password"] - if err := c.App.IsPasswordValid(password); err != nil { - c.Err = err - return - } - - email := props["email"] - if len(email) == 0 { - c.SetInvalidParam("oauthToEmail", "email") - return - } - - link, err := c.App.SwitchOAuthToEmail(email, password, c.Session.UserId) - if err != nil { - c.Err = err - return - } - - c.RemoveSessionCookie(w, r) - if c.Err != nil { - return - } - - c.LogAudit("success") - w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) -} - -func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - email := props["email"] - if len(email) == 0 { - c.SetInvalidParam("emailToLdap", "email") - return - } - - emailPassword := props["email_password"] - if len(emailPassword) == 0 { - c.SetInvalidParam("emailToLdap", "email_password") - return - } - - ldapId := props["ldap_id"] - if len(ldapId) == 0 { - c.SetInvalidParam("emailToLdap", "ldap_id") - return - } - - ldapPassword := props["ldap_password"] - if len(ldapPassword) == 0 { - c.SetInvalidParam("emailToLdap", "ldap_password") - return - } - - token := props["token"] - - c.LogAudit("attempt") - - link, err := c.App.SwitchEmailToLdap(email, emailPassword, token, ldapId, ldapPassword) - if err != nil { - c.Err = err - return - } - - c.RemoveSessionCookie(w, r) - if c.Err != nil { - return - } - - c.LogAudit("success") - w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) -} - -func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - email := props["email"] - if len(email) == 0 { - c.SetInvalidParam("ldapToEmail", "email") - return - } - - emailPassword := props["email_password"] - if err := c.App.IsPasswordValid(emailPassword); err != nil { - c.Err = err - return - } - - ldapPassword := props["ldap_password"] - if len(ldapPassword) == 0 { - c.SetInvalidParam("ldapToEmail", "ldap_password") - return - } - - token := props["token"] - - c.LogAudit("attempt") - - link, err := c.App.SwitchLdapToEmail(ldapPassword, token, email, emailPassword) - if err != nil { - c.Err = err - return - } - - c.RemoveSessionCookie(w, r) - if c.Err != nil { - return - } - - c.LogAudit("success") - w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link}))) -} - -func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) { - secret, err := c.App.GenerateMfaSecret(c.Session.UserId) - if err != nil { - c.Err = err - return - } - - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("Pragma", "no-cache") - w.Header().Set("Expires", "0") - w.Write([]byte(secret.ToJson())) -} - -func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.StringInterfaceFromJson(r.Body) - - activate, ok := props["activate"].(bool) - if !ok { - c.SetInvalidParam("updateMfa", "activate") - return - } - - token := "" - if activate { - token = props["token"].(string) - if len(token) == 0 { - c.SetInvalidParam("updateMfa", "token") - return - } - } - - c.LogAudit("attempt") - - if activate { - if err := c.App.ActivateMfa(c.Session.UserId, token); err != nil { - c.Err = err - return - } - c.LogAudit("success - activated") - } else { - if err := c.App.DeactivateMfa(c.Session.UserId); err != nil { - c.Err = err - return - } - c.LogAudit("success - deactivated") - } - - c.App.Go(func() { - var user *model.User - var err *model.AppError - if user, err = c.App.GetUser(c.Session.UserId); err != nil { - mlog.Warn(err.Error()) - return - } - - if err := c.App.SendMfaChangeEmail(user.Email, activate, user.Locale, c.App.GetSiteURL()); err != nil { - mlog.Error(err.Error()) - } - }) - - rdata := map[string]string{} - rdata["status"] = "ok" - w.Write([]byte(model.MapToJson(rdata))) -} - -func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { - if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication { - rdata := map[string]string{} - rdata["mfa_required"] = "false" - w.Write([]byte(model.MapToJson(rdata))) - return - } - - props := model.MapFromJson(r.Body) - - loginId := props["login_id"] - if len(loginId) == 0 { - c.SetInvalidParam("checkMfa", "login_id") - return - } - - rdata := map[string]string{} - if user, err := c.App.GetUserForLogin("", loginId); err != nil { - rdata["mfa_required"] = "false" - } else { - rdata["mfa_required"] = strconv.FormatBool(user.MfaActive) - } - w.Write([]byte(model.MapToJson(rdata))) -} - -func sanitizeProfile(c *Context, user *model.User) *model.User { - options := c.App.Config().GetSanitizeOptions() - - if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - options["email"] = true - options["fullname"] = true - options["authservice"] = true - } - - user.SanitizeProfile(options) - - return user -} - -func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.UserSearchFromJson(r.Body) - if props == nil { - c.SetInvalidParam("searchUsers", "") - return - } - - if len(props.Term) == 0 { - c.SetInvalidParam("searchUsers", "term") - return - } - - if props.InChannelId != "" && !c.App.SessionHasPermissionToChannel(c.Session, props.InChannelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if props.NotInChannelId != "" && !c.App.SessionHasPermissionToChannel(c.Session, props.NotInChannelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - searchOptions := map[string]bool{} - searchOptions[store.USER_SEARCH_OPTION_ALLOW_INACTIVE] = props.AllowInactive - - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - hideFullName := !c.App.Config().PrivacySettings.ShowFullName - hideEmail := !c.App.Config().PrivacySettings.ShowEmailAddress - - if hideFullName && hideEmail { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true - } else if hideFullName { - searchOptions[store.USER_SEARCH_OPTION_ALL_NO_FULL_NAME] = true - } else if hideEmail { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true - } - } - - if profiles, err := c.App.SearchUsers(props, searchOptions, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.UserListToJson(profiles))) - } -} - -func getProfilesByIds(c *Context, w http.ResponseWriter, r *http.Request) { - userIds := model.ArrayFromJson(r.Body) - - if len(userIds) == 0 { - c.SetInvalidParam("getProfilesByIds", "user_ids") - return - } - - if profiles, err := c.App.GetUsersByIds(userIds, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } else { - profileMap := map[string]*model.User{} - for _, p := range profiles { - profileMap[p.Id] = p - } - w.Write([]byte(model.UserMapToJson(profileMap))) - } -} - -func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - channelId := params["channel_id"] - teamId := params["team_id"] - - term := r.URL.Query().Get("term") - - if c.Session.GetTeamByTeamId(teamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - return - } - } - - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - searchOptions := map[string]bool{} - - hideFullName := !c.App.Config().PrivacySettings.ShowFullName - if hideFullName && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true - } else { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true - } - - autocomplete, err := c.App.AutocompleteUsersInChannel(teamId, channelId, term, searchOptions, c.IsSystemAdmin()) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(autocomplete.ToJson())) -} - -func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - teamId := params["team_id"] - - term := r.URL.Query().Get("term") - - if c.Session.GetTeamByTeamId(teamId) == nil { - if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - return - } - } - - searchOptions := map[string]bool{} - - hideFullName := !c.App.Config().PrivacySettings.ShowFullName - if hideFullName && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true - } else { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true - } - - autocomplete, err := c.App.AutocompleteUsersInTeam(teamId, term, searchOptions, c.IsSystemAdmin()) - if err != nil { - c.Err = err - return - } - - w.Write([]byte(autocomplete.ToJson())) -} - -func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) { - term := r.URL.Query().Get("term") - - searchOptions := map[string]bool{} - - hideFullName := !c.App.Config().PrivacySettings.ShowFullName - if hideFullName && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true - } else { - searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true - } - - var profiles []*model.User - var err *model.AppError - - if profiles, err = c.App.SearchUsersInTeam("", term, searchOptions, c.IsSystemAdmin()); err != nil { - c.Err = err - return - } - - w.Write([]byte(model.UserListToJson(profiles))) -} diff --git a/api/user_test.go b/api/user_test.go deleted file mode 100644 index 05ec0e096..000000000 --- a/api/user_test.go +++ /dev/null @@ -1,2737 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "image" - "image/color" - "io" - "mime/multipart" - "net/http" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" -) - -func TestCreateUser(t *testing.T) { - th := Setup() - defer th.TearDown() - - Client := th.CreateClient() - - user := model.User{Email: strings.ToLower("success+"+model.NewId()) + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "hello1", Username: "n" + model.NewId()} - - ruser, err := Client.CreateUser(&user, "") - if err != nil { - t.Fatal(err) - } - - Client.Login(user.Email, user.Password) - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - - if ruser.Data.(*model.User).Nickname != user.Nickname { - t.Fatal("nickname didn't match") - } - - if ruser.Data.(*model.User).Password != "" { - t.Fatal("password wasn't blank") - } - - if _, err := Client.CreateUser(ruser.Data.(*model.User), ""); err == nil { - t.Fatal("Cannot create an existing") - } - - ruser.Data.(*model.User).Id = "" - ruser.Data.(*model.User).Username = "n" + model.NewId() - ruser.Data.(*model.User).Password = "passwd1" - if _, err := Client.CreateUser(ruser.Data.(*model.User), ""); err != nil { - if err.Message != "An account with that email already exists." { - t.Fatal(err) - } - } - - ruser.Data.(*model.User).Email = "success+" + model.NewId() + "@simulator.amazonses.com" - ruser.Data.(*model.User).Username = user.Username - if _, err := Client.CreateUser(ruser.Data.(*model.User), ""); err != nil { - if err.Message != "An account with that username already exists." { - t.Fatal(err) - } - } - - ruser.Data.(*model.User).Email = "" - if _, err := Client.CreateUser(ruser.Data.(*model.User), ""); err != nil { - if err.Message != "Invalid email" { - t.Fatal(err) - } - } - - if _, err := Client.DoApiPost("/users/create", "garbage"); err == nil { - t.Fatal("should have been an error") - } -} - -func TestLogin(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.EmailSettings.EnableSignInWithEmail = false - *cfg.EmailSettings.EnableSignInWithUsername = false - *cfg.LdapSettings.Enable = false - }) - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE} - rteam2 := Client.Must(Client.CreateTeam(&team2)) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "corey" + model.NewId(), Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - if result, err := Client.LoginById(ruser.Data.(*model.User).Id, user.Password); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.User).Email != user.Email { - t.Fatal("emails didn't match") - } - } - - if _, err := Client.Login(user.Email, user.Password); err == nil { - t.Fatal("shouldn't be able to log in by email when disabled") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.EmailSettings.EnableSignInWithEmail = true }) - if result, err := Client.Login(user.Email, user.Password); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.User).Email != user.Email { - t.Fatal("emails didn't match") - } - } - - if _, err := Client.Login(user.Username, user.Password); err == nil { - t.Fatal("shouldn't be able to log in by username when disabled") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.EmailSettings.EnableSignInWithUsername = true }) - if result, err := Client.Login(user.Username, user.Password); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.User).Email != user.Email { - t.Fatal("emails didn't match") - } - } - - if _, err := Client.Login(user.Email, user.Password+"invalid"); err == nil { - t.Fatal("Invalid Password") - } - - if _, err := Client.Login(user.Username, user.Password+"invalid"); err == nil { - t.Fatal("Invalid Password") - } - - if _, err := Client.Login("", user.Password); err == nil { - t.Fatal("should have failed") - } - - if _, err := Client.Login("", user.Password); err == nil { - t.Fatal("should have failed") - } - - authToken := Client.AuthToken - Client.AuthToken = "invalid" - - if _, err := Client.GetUser(ruser.Data.(*model.User).Id, ""); err == nil { - t.Fatal("should have failed") - } - - Client.AuthToken = "" - - user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - - if _, err := Client.CreateUserFromSignup(&user2, "junk", "1231312"); err == nil { - t.Fatal("Should have errored, signed up without hashed email") - } - - token := model.NewToken( - app.TOKEN_TYPE_TEAM_INVITATION, - model.MapToJson(map[string]string{"teamId": rteam2.Data.(*model.Team).Id, "email": user2.Email}), - ) - <-th.App.Srv.Store.Token().Save(token) - props := make(map[string]string) - props["email"] = user2.Email - props["display_name"] = rteam2.Data.(*model.Team).DisplayName - data := model.MapToJson(props) - - ruser2, err := Client.CreateUserFromSignup(&user2, data, token.Token) - if err != nil { - t.Fatal(err) - } - if result := <-th.App.Srv.Store.Token().GetByToken(token.Token); result.Err == nil { - t.Fatal("The token must be deleted after be used") - } - - if _, err := Client.Login(ruser2.Data.(*model.User).Email, user2.Password); err != nil { - t.Fatal("From verified token") - } - - Client.AuthToken = authToken - - user3 := &model.User{ - Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", - Nickname: "Corey Hulen", - Username: "corey" + model.NewId(), - Password: "passwd1", - AuthService: model.USER_AUTH_SERVICE_LDAP, - } - ruser3 := Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser3.Id)) - - if _, err := Client.Login(ruser3.Id, user3.Password); err == nil { - t.Fatal("AD/LDAP user should not be able to log in with AD/LDAP disabled") - } -} - -func TestLoginUnverifiedEmail(t *testing.T) { - assert := assert.New(t) - - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.EmailSettings.EnableSignInWithEmail = true - cfg.EmailSettings.RequireEmailVerification = true - }) - - Client.Logout() - - user := &model.User{ - Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", - Nickname: "Corey Hulen", - Username: "corey" + model.NewId(), - Password: "passwd1", - EmailVerified: false, - } - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - - _, err := Client.Login(user.Email, user.Password+"invalid") - if assert.NotNil(err) { - assert.Equal("api.user.check_user_password.invalid.app_error", err.Id) - } - - _, err = Client.Login(user.Email, "passwd1") - if assert.NotNil(err) { - assert.Equal("api.user.login.not_verified.app_error", err.Id) - } -} - -func TestLoginByLdap(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "corey" + model.NewId(), Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - if _, err := Client.LoginByLdap(ruser.Data.(*model.User).Id, user.Password); err == nil { - t.Fatal("should have failed to log in with non AD/LDAP user") - } -} - -func TestLoginWithDeviceId(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user := th.BasicUser - Client.Must(Client.Logout()) - - deviceId := model.NewId() - if result, err := Client.LoginWithDevice(user.Email, user.Password, deviceId); err != nil { - t.Fatal(err) - } else { - ruser := result.Data.(*model.User) - - if ssresult, err := Client.GetSessions(ruser.Id); err != nil { - t.Fatal(err) - } else { - sessions := ssresult.Data.([]*model.Session) - if _, err := Client.LoginWithDevice(user.Email, user.Password, deviceId); err != nil { - t.Fatal(err) - } - - if sresult := <-th.App.Srv.Store.Session().Get(sessions[0].Id); sresult.Err == nil { - t.Fatal("session should have been removed") - } - } - } -} - -func TestPasswordGuessLockout(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user := th.BasicUser - Client.Must(Client.Logout()) - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.EmailSettings.EnableSignInWithEmail = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.MaximumLoginAttempts = 2 }) - - // OK to log in - if _, err := Client.Login(user.Username, user.Password); err != nil { - t.Fatal(err) - } - - Client.Must(Client.Logout()) - - // Fail twice - if _, err := Client.Login(user.Email, "notthepassword"); err == nil { - t.Fatal("Shouldn't be able to login with bad password.") - } - if _, err := Client.Login(user.Email, "notthepassword"); err == nil { - t.Fatal("Shouldn't be able to login with bad password.") - } - - // Locked out - if _, err := Client.Login(user.Email, user.Password); err == nil { - t.Fatal("Shouldn't be able to login with password when account is locked out.") - } -} - -func TestSessions(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user := th.BasicUser - Client.Must(Client.Logout()) - - deviceId := model.NewId() - Client.LoginWithDevice(user.Email, user.Password, deviceId) - Client.Login(user.Email, user.Password) - - r1, err := Client.GetSessions(user.Id) - if err != nil { - t.Fatal(err) - } - - sessions := r1.Data.([]*model.Session) - otherSession := "" - - if len(sessions) != 2 { - t.Fatal("invalid number of sessions") - } - - for _, session := range sessions { - if session.DeviceId == deviceId { - otherSession = session.Id - } - - if len(session.Token) != 0 { - t.Fatal("shouldn't return session tokens") - } - } - - if _, err := Client.RevokeSession(otherSession); err != nil { - t.Fatal(err) - } - - r2, err := Client.GetSessions(user.Id) - if err != nil { - t.Fatal(err) - } - - sessions2 := r2.Data.([]*model.Session) - - if len(sessions2) != 1 { - t.Fatal("invalid number of sessions") - } -} - -func TestGetUser(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam2, _ := Client.CreateTeam(&team2) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", FirstName: "Corey", LastName: "Hulen"} - ruser2, _ := Client.CreateUser(&user2, "") - th.LinkUserToTeam(ruser2.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id)) - - user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser3, _ := Client.CreateUser(&user3, "") - th.LinkUserToTeam(ruser3.Data.(*model.User), rteam2.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser3.Data.(*model.User).Id)) - - Client.Login(user.Email, user.Password) - - rId := ruser.Data.(*model.User).Id - if result, err := Client.GetUser(rId, ""); err != nil { - t.Fatal("Failed to get user") - } else { - if result.Data.(*model.User).Password != "" { - t.Fatal("User shouldn't have any password data once set") - } - - if cache_result, err := Client.GetUser(rId, result.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(*model.User) != nil { - t.Fatal("cache should be empty") - } - } - - if result, err := Client.GetMe(""); err != nil { - t.Fatal("Failed to get user") - } else { - if result.Data.(*model.User).Password != "" { - t.Fatal("User shouldn't have any password data once set") - } - } - - if _, err := Client.GetUser("FORBIDDENERROR", ""); err == nil { - t.Fatal("shouldn't exist") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false }) - - if result, err := Client.GetUser(ruser2.Data.(*model.User).Id, ""); err != nil { - t.Fatal(err) - } else { - u := result.Data.(*model.User) - if u.Password != "" { - t.Fatal("password must be empty") - } - if *u.AuthData != "" { - t.Fatal("auth data must be empty") - } - if u.Email != "" { - t.Fatal("email should be sanitized") - } - if u.FirstName != "" { - t.Fatal("full name should be sanitized") - } - if u.LastName != "" { - t.Fatal("full name should be sanitized") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = true }) - - if result, err := Client.GetUser(ruser2.Data.(*model.User).Id, ""); err != nil { - t.Fatal(err) - } else { - u := result.Data.(*model.User) - if u.Email == "" { - t.Fatal("email should not be sanitized") - } - if u.FirstName == "" { - t.Fatal("full name should not be sanitized") - } - if u.LastName == "" { - t.Fatal("full name should not be sanitized") - } - } - - if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 100, ""); err != nil { - t.Fatal(err) - } else if len(userMap.Data.(map[string]*model.User)) != 3 { - t.Fatal("should have been 3") - } else if userMap.Data.(map[string]*model.User)[rId].Id != rId { - t.Fatal("should have been valid") - } else { - - // test etag caching - if cache_result, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 100, userMap.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(map[string]*model.User) != nil { - t.Log(cache_result.Data) - t.Fatal("cache should be empty") - } - } - - if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 1, ""); err != nil { - t.Fatal(err) - } else if len(userMap.Data.(map[string]*model.User)) != 1 { - t.Fatal("should have been 1") - } - - if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 1, 1, ""); err != nil { - t.Fatal(err) - } else if len(userMap.Data.(map[string]*model.User)) != 1 { - t.Fatal("should have been 1") - } - - if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 10, 10, ""); err != nil { - t.Fatal(err) - } else if len(userMap.Data.(map[string]*model.User)) != 0 { - t.Fatal("should have been 0") - } - - if _, err := Client.GetProfilesInTeam(rteam2.Data.(*model.Team).Id, 0, 100, ""); err == nil { - t.Fatal("shouldn't have access") - } - - Client.AuthToken = "" - if _, err := Client.GetUser(ruser2.Data.(*model.User).Id, ""); err == nil { - t.Fatal("shouldn't have accss") - } - - th.App.UpdateUserRoles(ruser.Data.(*model.User).Id, model.SYSTEM_ADMIN_ROLE_ID, false) - - Client.Login(user.Email, "passwd1") - - if _, err := Client.GetProfilesInTeam(rteam2.Data.(*model.Team).Id, 0, 100, ""); err != nil { - t.Fatal(err) - } -} - -func TestGetProfiles(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id)) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - - if result, err := th.BasicClient.GetProfiles(0, 100, ""); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) < 1 { - t.Fatal("map was wrong length") - } - - for _, user := range users { - if user.Email == "" { - t.Fatal("problem with show email") - } - } - - // test etag caching - if cache_result, err := th.BasicClient.GetProfiles(0, 100, result.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(map[string]*model.User) != nil { - t.Log(cache_result.Etag) - t.Log(result.Etag) - t.Fatal("cache should be empty") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - - if result, err := th.BasicClient.GetProfiles(0, 100, ""); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) < 1 { - t.Fatal("map was wrong length") - } - - for _, user := range users { - if user.Email != "" { - t.Fatal("problem with show email") - } - } - } -} - -func TestGetProfilesByIds(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - - if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) != 1 { - t.Fatal("map was wrong length") - } - - for _, user := range users { - if user.Email == "" { - t.Fatal("problem with show email") - } - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - - if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) != 1 { - t.Fatal("map was wrong length") - } - - for _, user := range users { - if user.Email != "" { - t.Fatal("problem with show email") - } - } - } - - if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id, th.BasicUser2.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) != 2 { - t.Fatal("map was wrong length") - } - } -} - -func TestGetAudits(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - time.Sleep(100 * time.Millisecond) - - Client.Login(user.Email, user.Password) - - time.Sleep(100 * time.Millisecond) - - if result, err := Client.GetAudits(ruser.Data.(*model.User).Id, ""); err != nil { - t.Fatal(err) - } else { - - if len(result.Data.(model.Audits)) != 1 { - t.Fatal(result.Data.(model.Audits)) - } - - if cache_result, err := Client.GetAudits(ruser.Data.(*model.User).Id, result.Etag); err != nil { - t.Fatal(err) - } else if cache_result.Data.(model.Audits) != nil { - t.Fatal("cache should be empty") - } - } - - if _, err := Client.GetAudits("FORBIDDENERROR", ""); err == nil { - t.Fatal("audit log shouldn't exist") - } -} - -func TestUserCreateImage(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - b, err := app.CreateProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba", th.App.Config().FileSettings.InitialFont) - if err != nil { - t.Fatal(err) - } - - rdr := bytes.NewReader(b) - img, _, err2 := image.Decode(rdr) - if err2 != nil { - t.Fatal(err) - } - - colorful := color.RGBA{116, 49, 196, 255} - - if img.At(1, 1) != colorful { - t.Fatal("Failed to create correct color") - } - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - - if resp, err := Client.DoApiGet("/users/"+user.Id+"/image", "", ""); err != nil { - t.Fatal(err) - } else { - etag := resp.Header.Get(model.HEADER_ETAG_SERVER) - resp2, _ := Client.DoApiGet("/users/"+user.Id+"/image", "", etag) - if resp2.StatusCode == 304 { - t.Fatal("Shouldn't have hit etag") - } - } - - if *th.App.Config().FileSettings.DriverName == model.IMAGE_DRIVER_S3 { - endpoint := th.App.Config().FileSettings.AmazonS3Endpoint - accessKey := th.App.Config().FileSettings.AmazonS3AccessKeyId - secretKey := th.App.Config().FileSettings.AmazonS3SecretAccessKey - secure := *th.App.Config().FileSettings.AmazonS3SSL - signV2 := *th.App.Config().FileSettings.AmazonS3SignV2 - region := th.App.Config().FileSettings.AmazonS3Region - s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2, region) - if err != nil { - t.Fatal(err) - } - bucket := th.App.Config().FileSettings.AmazonS3Bucket - if err = s3Clnt.RemoveObject(bucket, "/users/"+user.Id+"/profile.png"); err != nil { - t.Fatal(err) - } - } else { - path := th.App.Config().FileSettings.Directory + "/users/" + user.Id + "/profile.png" - if err := os.Remove(path); err != nil { - t.Fatal("Couldn't remove file at " + path) - } - } -} - -func TestUserUploadProfileImage(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - if *th.App.Config().FileSettings.DriverName != "" { - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil { - t.Fatal("Should have errored") - } - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil { - t.Fatal("Should have errored") - } - - part, err := writer.CreateFormFile("blargh", "test.png") - if err != nil { - t.Fatal(err) - } - - path, _ := utils.FindDir("tests") - file, err := os.Open(filepath.Join(path, "test.png")) - if err != nil { - t.Fatal(err) - } - defer file.Close() - - _, err = io.Copy(part, file) - if err != nil { - t.Fatal(err) - } - - if err := writer.Close(); err != nil { - t.Fatal(err) - } - - if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil { - t.Fatal("Should have errored") - } - - file2, err := os.Open(path + "/test.png") - if err != nil { - t.Fatal(err) - } - defer file2.Close() - - body = &bytes.Buffer{} - writer = multipart.NewWriter(body) - - part, err = writer.CreateFormFile("image", "test.png") - if err != nil { - t.Fatal(err) - } - - if _, err := io.Copy(part, file2); err != nil { - t.Fatal(err) - } - - if err := writer.Close(); err != nil { - t.Fatal(err) - } - - if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr != nil { - t.Fatal(upErr) - } - - Client.DoApiGet("/users/"+user.Id+"/image", "", "") - - if *th.App.Config().FileSettings.DriverName == model.IMAGE_DRIVER_S3 { - endpoint := th.App.Config().FileSettings.AmazonS3Endpoint - accessKey := th.App.Config().FileSettings.AmazonS3AccessKeyId - secretKey := th.App.Config().FileSettings.AmazonS3SecretAccessKey - secure := *th.App.Config().FileSettings.AmazonS3SSL - signV2 := *th.App.Config().FileSettings.AmazonS3SignV2 - region := th.App.Config().FileSettings.AmazonS3Region - s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2, region) - if err != nil { - t.Fatal(err) - } - bucket := th.App.Config().FileSettings.AmazonS3Bucket - if err = s3Clnt.RemoveObject(bucket, "/users/"+user.Id+"/profile.png"); err != nil { - t.Fatal(err) - } - } else { - path := th.App.Config().FileSettings.Directory + "users/" + user.Id + "/profile.png" - if err := os.Remove(path); err != nil { - t.Fatal("Couldn't remove file at " + path) - } - } - } else { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr.StatusCode != http.StatusNotImplemented { - t.Fatal("Should have failed with 501 - Not Implemented") - } - } -} - -func TestUserUpdate(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", Roles: ""} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - if _, err := Client.UpdateUser(user); err == nil { - t.Fatal("Should have errored") - } - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - user.Nickname = "Jim Jimmy" - user.Roles = model.SYSTEM_ADMIN_ROLE_ID - user.LastPasswordUpdate = 123 - - if result, err := Client.UpdateUser(user); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.User).Nickname != "Jim Jimmy" { - t.Fatal("Nickname did not update properly") - } - if result.Data.(*model.User).Roles != model.SYSTEM_USER_ROLE_ID { - t.Fatal("Roles should not have updated") - } - if result.Data.(*model.User).LastPasswordUpdate == 123 { - t.Fatal("LastPasswordUpdate should not have updated") - } - } - - user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - Client.Login(user2.Email, "passwd1") - Client.SetTeamId(team.Id) - - user.Nickname = "Tim Timmy" - - if _, err := Client.UpdateUser(user); err == nil { - t.Fatal("Should have errored") - } -} - -func TestUserUpdatePassword(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - Client.SetTeamId(team.Id) - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - if _, err := Client.UpdateUserPassword(user.Id, "passwd1", "newpasswd1"); err == nil { - t.Fatal("Should have errored") - } - - Client.Login(user.Email, "passwd1") - - if _, err := Client.UpdateUserPassword("123", "passwd1", "newpwd"); err == nil { - t.Fatal("Should have errored") - } - - if _, err := Client.UpdateUserPassword(user.Id, "", "newpwd"); err == nil { - t.Fatal("Should have errored") - } - - if _, err := Client.UpdateUserPassword(user.Id, "passwd1", "npwd"); err == nil { - t.Fatal("Should have errored") - } - - if _, err := Client.UpdateUserPassword("12345678901234567890123456", "passwd1", "newpwd1"); err == nil { - t.Fatal("Should have errored") - } - - if _, err := Client.UpdateUserPassword(user.Id, "badpwd", "newpwd"); err == nil { - t.Fatal("Should have errored") - } - - if _, err := Client.UpdateUserPassword(user.Id, "passwd1", "newpwd1"); err != nil { - t.Fatal(err) - } - - updatedUser := Client.Must(Client.GetUser(user.Id, "")).Data.(*model.User) - if updatedUser.LastPasswordUpdate == user.LastPasswordUpdate { - t.Fatal("LastPasswordUpdate should have changed") - } - - if _, err := Client.Login(user.Email, "newpwd1"); err != nil { - t.Fatal(err) - } - - // Test lockout - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.MaximumLoginAttempts = 2 }) - - // Fail twice - if _, err := Client.UpdateUserPassword(user.Id, "badpwd", "newpwd"); err == nil { - t.Fatal("Should have errored") - } - if _, err := Client.UpdateUserPassword(user.Id, "badpwd", "newpwd"); err == nil { - t.Fatal("Should have errored") - } - - // Should fail because account is locked out - if _, err := Client.UpdateUserPassword(user.Id, "newpwd1", "newpwd2"); err == nil { - t.Fatal("Should have errored") - } - - user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - - Client.Login(user2.Email, "passwd1") - - if _, err := Client.UpdateUserPassword(user.Id, "passwd1", "newpwd"); err == nil { - t.Fatal("Should have errored") - } -} - -func TestUserUpdateRoles(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - if _, err := Client.UpdateUserRoles(user.Id, ""); err == nil { - t.Fatal("Should have errored, not logged in") - } - - Client.Login(user2.Email, "passwd1") - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateUserRoles(user.Id, ""); err == nil { - t.Fatal("Should have errored, not admin") - } - - team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - - user3 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) - th.LinkUserToTeam(user3, team2) - store.Must(th.App.Srv.Store.User().VerifyEmail(user3.Id)) - - Client.Login(user3.Email, "passwd1") - Client.SetTeamId(team2.Id) - - if _, err := Client.UpdateUserRoles(user2.Id, ""); err == nil { - t.Fatal("Should have errored, wrong team") - } - - Client.Login(user.Email, "passwd1") - - if _, err := Client.UpdateUserRoles("junk", ""); err == nil { - t.Fatal("Should have errored, bad id") - } - - if _, err := Client.UpdateUserRoles("system_admin", ""); err == nil { - t.Fatal("Should have errored, we want to avoid this mistake") - } - - if _, err := Client.UpdateUserRoles("12345678901234567890123456", ""); err == nil { - t.Fatal("Should have errored, bad id") - } - - if _, err := Client.UpdateUserRoles(user2.Id, "junk"); err == nil { - t.Fatal("Should have errored, bad role") - } -} - -func TestUserUpdateRolesMoreCases(t *testing.T) { - th := Setup().InitSystemAdmin().InitBasic() - defer th.TearDown() - - th.SystemAdminClient.SetTeamId(th.BasicTeam.Id) - th.LinkUserToTeam(th.SystemAdminUser, th.BasicTeam) - - const BASIC_USER = "system_user" - const SYSTEM_ADMIN = "system_user system_admin" - - // user 1 is trying to promote user 2 - if _, err := th.BasicClient.UpdateUserRoles(th.BasicUser2.Id, SYSTEM_ADMIN); err == nil { - t.Fatal("Should have errored, basic user is not a system admin") - } - - // user 1 is trying to demote system admin - if _, err := th.BasicClient.UpdateUserRoles(th.SystemAdminUser.Id, BASIC_USER); err == nil { - t.Fatal("Should have errored, can only be system admin") - } - - // user 1 is trying to promote himself - if _, err := th.BasicClient.UpdateUserRoles(th.BasicUser.Id, SYSTEM_ADMIN); err == nil { - t.Fatal("Should have errored, can only be system admin") - } - - // System admin promoting user 2 - if _, err := th.SystemAdminClient.UpdateUserRoles(th.BasicUser2.Id, SYSTEM_ADMIN); err != nil { - t.Fatal("Should have succeeded since they are system admin") - } - - // System admin demoting user 2 - if _, err := th.SystemAdminClient.UpdateUserRoles(th.BasicUser2.Id, BASIC_USER); err != nil { - t.Fatal("Should have succeeded since they are system admin") - } - - // Setting user to team admin should have no effect on results - th.BasicClient.Must(th.SystemAdminClient.UpdateTeamRoles(th.BasicUser.Id, "team_user team_admin")) - - // user 1 is trying to promote user 2 - if _, err := th.BasicClient.UpdateUserRoles(th.BasicUser2.Id, SYSTEM_ADMIN); err == nil { - t.Fatal("Should have errored, basic user is not a system admin") - } - - // user 1 is trying to demote system admin - if _, err := th.BasicClient.UpdateUserRoles(th.SystemAdminUser.Id, BASIC_USER); err == nil { - t.Fatal("Should have errored, can only be system admin") - } - - // user 1 is trying to promote himself - if _, err := th.BasicClient.UpdateUserRoles(th.BasicUser.Id, SYSTEM_ADMIN); err == nil { - t.Fatal("Should have errored, can only be system admin") - } - - // system admin demoting himself - if _, err := th.SystemAdminClient.UpdateUserRoles(th.SystemAdminUser.Id, BASIC_USER); err != nil { - t.Fatal("Should have succeeded since they are system admin") - } -} - -func TestUserUpdateDeviceId(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890" - - if _, err := Client.AttachDeviceId(deviceId); err != nil { - t.Fatal(err) - } - - if result := <-th.App.Srv.Store.Session().GetSessions(user.Id); result.Err != nil { - t.Fatal(result.Err) - } else { - sessions := result.Data.([]*model.Session) - - if sessions[0].DeviceId != deviceId { - t.Fatal("Missing device Id") - } - } -} - -func TestUserUpdateDeviceId2(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - deviceId := model.PUSH_NOTIFY_APPLE_REACT_NATIVE + ":1234567890" - - if _, err := Client.AttachDeviceId(deviceId); err != nil { - t.Fatal(err) - } - - if result := <-th.App.Srv.Store.Session().GetSessions(user.Id); result.Err != nil { - t.Fatal(result.Err) - } else { - sessions := result.Data.([]*model.Session) - - if sessions[0].DeviceId != deviceId { - t.Fatal("Missing device Id") - } - } -} - -func TestUserUpdateActive(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - SystemAdminClient := th.SystemAdminClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - if _, err := Client.UpdateActive(user.Id, false); err == nil { - t.Fatal("Should have errored, not logged in") - } - - Client.Login(user2.Email, "passwd1") - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateActive(user.Id, false); err == nil { - t.Fatal("Should have errored, not admin") - } - - Client.Must(Client.Logout()) - - user3 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team2) - store.Must(th.App.Srv.Store.User().VerifyEmail(user3.Id)) - - Client.Login(user3.Email, "passwd1") - Client.SetTeamId(team2.Id) - - if _, err := Client.UpdateActive(user.Id, false); err == nil { - t.Fatal("Should have errored, not yourself") - } - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateActive("junk", false); err == nil { - t.Fatal("Should have errored, bad id") - } - - if _, err := Client.UpdateActive("12345678901234567890123456", false); err == nil { - t.Fatal("Should have errored, bad id") - } - - th.App.SetStatusOnline(user3.Id, "", false) - - if _, err := SystemAdminClient.UpdateActive(user3.Id, false); err != nil { - t.Fatal(err) - } - - if status, err := th.App.GetStatus(user3.Id); err != nil { - t.Fatal(err) - } else if status.Status != model.STATUS_OFFLINE { - t.Fatal("status should have been set to offline") - } -} - -func TestUserPermDelete(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user1 := &model.User{Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) - th.LinkUserToTeam(user1, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user1.Id)) - - Client.Login(user1.Email, "passwd1") - Client.SetTeamId(team.Id) - - channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - - post1 := &model.Post{ChannelId: channel1.Id, Message: "search for post1"} - post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) - - post2 := &model.Post{ChannelId: channel1.Id, Message: "search for post2"} - post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post) - - post3 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag search for post3"} - post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post) - - post4 := &model.Post{ChannelId: channel1.Id, Message: "hashtag for post4"} - post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) - - c := &Context{} - c.RequestId = model.NewId() - c.IpAddress = "test" - - err := th.App.PermanentDeleteUser(user1) - if err != nil { - t.Fatal(err) - } - - Client.ClearOAuthToken() -} - -func TestSendPasswordReset(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - Client.Logout() - - if result, err := Client.SendPasswordReset(user.Email); err != nil { - t.Fatal(err) - } else { - resp := result.Data.(map[string]string) - if resp["email"] != user.Email { - t.Fatal("wrong email") - } - } - - if _, err := Client.SendPasswordReset("junk@junk.com"); err != nil { - t.Fatal("Should have errored - bad email") - } - - if _, err := Client.SendPasswordReset(""); err == nil { - t.Fatal("Should have errored - no email") - } - - authData := model.NewId() - user2 := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", AuthData: &authData, AuthService: "random"} - user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - th.LinkUserToTeam(user2, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user2.Id)) - - if _, err := Client.SendPasswordReset(user2.Email); err == nil { - t.Fatal("should have errored - SSO user can't send reset password link") - } -} - -/*func TestResetPassword(t *testing.T) { - th := Setup().InitSystemAdmin() - Client := th.SystemAdminClient - team := th.SystemAdminTeam - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - //Delete all the messages before check the reset password - utils.DeleteMailBox(user.Email) - - Client.Must(Client.SendPasswordReset(user.Email)) - - //Check if the email was send to the rigth email address and the recovery key match - var resultsMailbox utils.JSONMessageHeaderInbucket - err := utils.RetryInbucket(5, func() error { - var err error - resultsMailbox, err = utils.GetMailBox(user.Email) - return err - }) - if err != nil { - t.Log(err) - t.Log("No email was received, maybe due load on the server. Disabling this verification") - } - - var recoveryTokenString string - if err == nil && len(resultsMailbox) > 0 { - if !strings.ContainsAny(resultsMailbox[0].To[0], user.Email) { - t.Fatal("Wrong To recipient") - } else { - if resultsEmail, err := utils.GetMessageFromMailbox(user.Email, resultsMailbox[0].ID); err == nil { - loc := strings.Index(resultsEmail.Body.Text, "token=") - if loc == -1 { - t.Log(recoveryTokenString) - t.Log(resultsEmail.Body.Text) - t.Fatal("Code not found in email") - } - loc += 6 - recoveryTokenString = resultsEmail.Body.Text[loc : loc+model.TOKEN_SIZE] - t.Log(resultsEmail.Body.Text) - } - } - } - - var recoveryToken *model.Token - if result := <-th.App.Srv.Store.Token().GetByToken(recoveryTokenString); result.Err != nil { - t.Log(recoveryTokenString) - t.Fatal(result.Err) - } else { - recoveryToken = result.Data.(*model.Token) - } - - if recoveryToken.Token != recoveryTokenString { - t.Fatal("Did not send the correct token. DB: "+recoveryToken.Token, " Sent: "+recoveryTokenString) - } - - if _, err := Client.ResetPassword(recoveryToken.Token, ""); err == nil { - t.Fatal("Should have errored - no password") - } - - if _, err := Client.ResetPassword(recoveryToken.Token, "newp"); err == nil { - t.Fatal("Should have errored - password too short") - } - - if _, err := Client.ResetPassword("", "newpwd"); err == nil { - t.Fatal("Should have errored - no code") - } - - if _, err := Client.ResetPassword("junk", "newpwd"); err == nil { - t.Fatal("Should have errored - bad code") - } - - code := "" - for i := 0; i < model.TOKEN_SIZE; i++ { - code += "a" - } - if _, err := Client.ResetPassword(code, "newpwd1"); err == nil { - t.Fatal("Should have errored - bad code") - } - - if _, err := Client.ResetPassword(recoveryToken.Token, "newpwd1"); err != nil { - t.Log(recoveryToken.Token) - t.Fatal(err) - } - -}*/ - -func TestUserUpdateNotify(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - - Client.Logout() - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", Roles: ""} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, team) - store.Must(th.App.Srv.Store.User().VerifyEmail(user.Id)) - - data := make(map[string]string) - data["user_id"] = user.Id - data["email"] = "true" - data["desktop"] = "all" - data["desktop_sound"] = "false" - data["comments"] = "any" - - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - not logged in") - } - - Client.Login(user.Email, "passwd1") - Client.SetTeamId(team.Id) - - if result, err := Client.UpdateUserNotify(data); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.User).NotifyProps["desktop"] != data["desktop"] { - t.Fatal("NotifyProps did not update properly - desktop") - } - if result.Data.(*model.User).NotifyProps["desktop_sound"] != data["desktop_sound"] { - t.Fatal("NotifyProps did not update properly - desktop_sound") - } - if result.Data.(*model.User).NotifyProps["email"] != data["email"] { - t.Fatal("NotifyProps did not update properly - email") - } - if result.Data.(*model.User).NotifyProps["comments"] != data["comments"] { - t.Fatal("NotifyProps did not update properly - comments") - } - } - - if _, err := Client.UpdateUserNotify(nil); err == nil { - t.Fatal("Should have errored") - } - - data["user_id"] = "junk" - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - junk user id") - } - - data["user_id"] = "12345678901234567890123456" - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - bad user id") - } - - data["user_id"] = user.Id - data["desktop"] = "" - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - empty desktop notify") - } - - data["desktop"] = "all" - data["desktop_sound"] = "" - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - empty desktop sound") - } - - data["desktop_sound"] = "false" - data["email"] = "" - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - empty email") - } - - data["email"] = "true" - data["comments"] = "" - if _, err := Client.UpdateUserNotify(data); err == nil { - t.Fatal("Should have errored - empty comments") - } -} - -func TestFuzzyUserCreate(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - Client.Logout() - - for i := 0; i < len(utils.FUZZY_STRINGS_NAMES) || i < len(utils.FUZZY_STRINGS_EMAILS); i++ { - testName := "Name" - testEmail := "test@nowhere.com" - - if i < len(utils.FUZZY_STRINGS_NAMES) { - testName = utils.FUZZY_STRINGS_NAMES[i] - } - if i < len(utils.FUZZY_STRINGS_EMAILS) { - testEmail = utils.FUZZY_STRINGS_EMAILS[i] - } - - user := model.User{Email: strings.ToLower(model.NewId()) + testEmail, Nickname: testName, Password: "hello1"} - - ruser, err := Client.CreateUser(&user, "") - if err != nil { - t.Fatal(err) - } - - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - } -} - -func TestEmailToOAuth(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) - th.LinkUserToTeam(ruser, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) - - m := map[string]string{} - if _, err := Client.EmailToOAuth(m); err == nil { - t.Fatal("should have failed - empty data") - } - - m["password"] = "passwd1" - _, err := Client.EmailToOAuth(m) - if err == nil { - t.Fatal("should have failed - missing team_name, service, email") - } - - m["team_name"] = team.Name - if _, err := Client.EmailToOAuth(m); err == nil { - t.Fatal("should have failed - missing service, email") - } - - m["service"] = "someservice" - if _, err := Client.EmailToOAuth(m); err == nil { - t.Fatal("should have failed - missing email") - } - - m["team_name"] = "junk" - if _, err := Client.EmailToOAuth(m); err == nil { - t.Fatal("should have failed - bad team name") - } - - m["team_name"] = team.Name - m["email"] = "junk" - if _, err := Client.EmailToOAuth(m); err == nil { - t.Fatal("should have failed - bad email") - } - - m["email"] = ruser.Email - m["password"] = "junk" - if _, err := Client.EmailToOAuth(m); err == nil { - t.Fatal("should have failed - bad password") - } -} - -func TestOAuthToEmail(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) - th.LinkUserToTeam(ruser, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) - - user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User) - th.LinkUserToTeam(ruser2, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Id)) - - m := map[string]string{} - if _, err := Client.OAuthToEmail(m); err == nil { - t.Fatal("should have failed - not logged in") - } - - Client.Login(user.Email, user.Password) - - if _, err := Client.OAuthToEmail(m); err == nil { - t.Fatal("should have failed - empty data") - } - - m["password"] = "passwd1" - _, err := Client.OAuthToEmail(m) - if err == nil { - t.Fatal("should have failed - missing team_name, service, email") - } - - m["team_name"] = team.Name - if _, err := Client.OAuthToEmail(m); err == nil { - t.Fatal("should have failed - missing email") - } - - m["team_name"] = team.Name - m["email"] = "junk" - if _, err := Client.OAuthToEmail(m); err == nil { - t.Fatal("should have failed - bad email") - } - - m["email"] = ruser2.Email - if _, err := Client.OAuthToEmail(m); err == nil { - t.Fatal("should have failed - wrong user") - } -} - -func TestLDAPToEmail(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) - th.LinkUserToTeam(ruser, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) - - Client.Login(user.Email, user.Password) - - m := map[string]string{} - if _, err := Client.LDAPToEmail(m); err == nil { - t.Fatal("should have failed - empty data") - } - - m["email_password"] = "passwd1" - _, err := Client.LDAPToEmail(m) - if err == nil { - t.Fatal("should have failed - missing team_name, ldap_password, email") - } - - m["team_name"] = team.Name - if _, err := Client.LDAPToEmail(m); err == nil { - t.Fatal("should have failed - missing email, ldap_password") - } - - m["ldap_password"] = "passwd1" - if _, err := Client.LDAPToEmail(m); err == nil { - t.Fatal("should have failed - missing email") - } - - m["email"] = ruser.Email - m["team_name"] = "junk" - if _, err := Client.LDAPToEmail(m); err == nil { - t.Fatal("should have failed - bad team name") - } - - m["team_name"] = team.Name - m["email"] = "junk" - if _, err := Client.LDAPToEmail(m); err == nil { - t.Fatal("should have failed - bad email") - } - - m["email"] = user.Email - if _, err := Client.LDAPToEmail(m); err == nil { - t.Fatal("should have failed - user is not an AD/LDAP user") - } -} - -func TestEmailToLDAP(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) - th.LinkUserToTeam(ruser, rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) - - Client.Login(user.Email, user.Password) - - m := map[string]string{} - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - empty data") - } - - m["email_password"] = "passwd1" - _, err := Client.EmailToLDAP(m) - if err == nil { - t.Fatal("should have failed - missing team_name, ldap_id, ldap_password, email") - } - - m["team_name"] = team.Name - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - missing email, ldap_password, ldap_id") - } - - m["ldap_id"] = "someid" - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - missing email, ldap_password") - } - - m["ldap_password"] = "passwd1" - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - missing email") - } - - m["email"] = ruser.Email - m["team_name"] = "junk" - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - bad team name") - } - - m["team_name"] = team.Name - m["email"] = "junk" - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - bad email") - } - - m["email"] = user.Email - m["email_password"] = "junk" - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - bad password") - } - - m["email_password"] = "passwd1" - if _, err := Client.EmailToLDAP(m); err == nil { - t.Fatal("should have failed - missing ldap bits or user") - } -} - -func TestMeInitialLoad(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - if result, err := th.BasicClient.GetInitialLoad(); err != nil { - t.Fatal(err) - } else { - il := result.Data.(*model.InitialLoad) - - if il.User == nil { - t.Fatal("should be valid") - } - - if il.Preferences == nil { - t.Fatal("should be valid") - } - - if len(il.Teams) != 1 { - t.Fatal("should be valid") - } - - if len(il.TeamMembers) != 1 { - t.Fatal("should be valid") - } - - if len(il.ClientCfg) == 0 { - t.Fatal("should be valid") - } - - if len(il.LicenseCfg) == 0 { - t.Fatal("should be valid") - } - } - - th.BasicClient.Logout() - - if result, err := th.BasicClient.GetInitialLoad(); err != nil { - t.Fatal(err) - } else { - il := result.Data.(*model.InitialLoad) - - if il.User != nil { - t.Fatal("should be valid") - } - - if il.Preferences != nil { - t.Fatal("should be valid") - } - - if len(il.Teams) != 0 { - t.Fatal("should be valid") - } - - if len(il.TeamMembers) != 0 { - t.Fatal("should be valid") - } - - if len(il.ClientCfg) == 0 { - t.Fatal("should be valid") - } - - if len(il.LicenseCfg) == 0 { - t.Fatal("should be valid") - } - } - -} - -func TestGenerateMfaSecret(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - Client.Logout() - - if _, err := Client.GenerateMfaSecret(); err == nil { - t.Fatal("should have failed - not logged in") - } - - Client.Login(user.Email, user.Password) - - if _, err := Client.GenerateMfaSecret(); err == nil { - t.Fatal("should have failed - not licensed") - } - - // need to add more test cases when license and config can be configured for tests -} - -func TestUpdateMfa(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - th.App.SetLicense(nil) - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - Client.Logout() - - if _, err := Client.UpdateMfa(true, "123456"); err == nil { - t.Fatal("should have failed - not logged in") - } - - Client.Login(user.Email, user.Password) - - if _, err := Client.UpdateMfa(true, ""); err == nil { - t.Fatal("should have failed - no token") - } - - if _, err := Client.UpdateMfa(true, "123456"); err == nil { - t.Fatal("should have failed - not licensed") - } - - th.App.SetLicense(model.NewTestLicense("mfa")) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) - - if _, err := Client.UpdateMfa(true, "123456"); err == nil { - t.Fatal("should have failed - bad token") - } - - // need to add more test cases when enterprise bits can be loaded into tests -} - -func TestCheckMfa(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - Client.Logout() - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user, "") - th.LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) - store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - - if result, err := Client.CheckMfa(user.Email); err != nil { - t.Fatal(err) - } else { - resp := result.Data.(map[string]string) - if resp["mfa_required"] != "false" { - t.Fatal("mfa should not be required") - } - } - - // need to add more test cases when enterprise bits can be loaded into tests -} - -func TestUserTyping(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - WebSocketClient, err := th.CreateWebSocketClient() - if err != nil { - t.Fatal(err) - } - defer WebSocketClient.Close() - WebSocketClient.Listen() - - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { - t.Fatal("should have responded OK to authentication challenge") - } - - WebSocketClient.UserTyping("", "") - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.websocket_handler.invalid_param.app_error" { - t.Fatal("should have been invalid param response") - } - - th.LoginBasic2() - Client.Must(Client.JoinChannel(th.BasicChannel.Id)) - - WebSocketClient2, err2 := th.CreateWebSocketClient() - if err2 != nil { - t.Fatal(err2) - } - defer WebSocketClient2.Close() - WebSocketClient2.Listen() - - time.Sleep(300 * time.Millisecond) - - WebSocketClient.UserTyping(th.BasicChannel.Id, "") - - time.Sleep(300 * time.Millisecond) - - stop := make(chan bool) - eventHit := false - - go func() { - for { - select { - case resp := <-WebSocketClient2.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == th.BasicUser.Id { - eventHit = true - } - case <-stop: - return - } - } - }() - - time.Sleep(1000 * time.Millisecond) - - stop <- true - - if !eventHit { - t.Fatal("did not receive typing event") - } - - WebSocketClient.UserTyping(th.BasicChannel.Id, "someparentid") - - time.Sleep(300 * time.Millisecond) - - eventHit = false - - go func() { - for { - select { - case resp := <-WebSocketClient2.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["parent_id"] == "someparentid" { - eventHit = true - } - case <-stop: - return - } - } - }() - - time.Sleep(300 * time.Millisecond) - - stop <- true - - if !eventHit { - t.Fatal("did not receive typing event") - } -} - -func TestGetProfilesInChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - - if result, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) < 1 { - t.Fatal("map was wrong length") - } - - for _, user := range users { - if user.Email == "" { - t.Fatal("problem with show email") - } - } - } - - th.LoginBasic2() - - if _, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil { - t.Fatal("should not have access") - } - - Client.Must(Client.JoinChannel(th.BasicChannel.Id)) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - - if result, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) < 1 { - t.Fatal("map was wrong length") - } - - found := false - for _, user := range users { - if user.Email != "" { - t.Fatal("problem with show email") - } - if user.Id == th.BasicUser2.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - Client.Must(Client.CreateUser(&user, "")) - - Client.Login(user.Email, "passwd1") - Client.SetTeamId("junk") - - if _, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil { - t.Fatal("should not have access") - } -} - -func TestGetProfilesNotInChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - - if result, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) < 1 { - t.Fatal("map was wrong length") - } - - found := false - for _, user := range users { - if user.Email == "" { - t.Fatal("problem with show email") - } - if user.Id == th.BasicUser2.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) - th.LinkUserToTeam(user, th.BasicTeam) - - th.LoginBasic2() - - if _, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil { - t.Fatal("should not have access") - } - - Client.Must(Client.JoinChannel(th.BasicChannel.Id)) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - - if result, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil { - t.Fatal(err) - } else { - users := result.Data.(map[string]*model.User) - - if len(users) < 1 { - t.Fatal("map was wrong length") - } - - found := false - for _, user := range users { - if user.Email != "" { - t.Fatal("problem with show email") - } - if user.Id == th.BasicUser2.Id { - found = true - } - } - - if found { - t.Fatal("should not have found profile") - } - } - - user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - Client.Must(Client.CreateUser(&user2, "")) - - Client.Login(user2.Email, "passwd1") - Client.SetTeamId(th.BasicTeam.Id) - - if _, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil { - t.Fatal("should not have access") - } -} - -func TestSearchUsers(t *testing.T) { - th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() - - Client := th.BasicClient - - inactiveUser := th.CreateUser(Client) - th.LinkUserToTeam(inactiveUser, th.BasicTeam) - th.SystemAdminClient.Must(th.SystemAdminClient.UpdateActive(inactiveUser.Id, false)) - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == th.BasicUser.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: inactiveUser.Username, TeamId: th.BasicTeam.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == inactiveUser.Id { - found = true - } - } - - if found { - t.Fatal("should not have found inactive user") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: inactiveUser.Username, TeamId: th.BasicTeam.Id, AllowInactive: true}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == inactiveUser.Id { - found = true - } - } - - if !found { - t.Fatal("should have found inactive user") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, InChannelId: th.BasicChannel.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - if len(users) != 1 { - t.Fatal("map was wrong length") - } - - found := false - for _, user := range users { - if user.Id == th.BasicUser.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser2.Username, NotInChannelId: th.BasicChannel.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - if len(users) != 1 { - t.Fatal("map was wrong length") - } - - found1 := false - found2 := false - for _, user := range users { - if user.Id == th.BasicUser.Id { - found1 = true - } else if user.Id == th.BasicUser2.Id { - found2 = true - } - } - - if found1 { - t.Fatal("should not have found profile") - } - if !found2 { - t.Fatal("should have found profile") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser2.Username, TeamId: th.BasicTeam.Id, NotInChannelId: th.BasicChannel.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - if len(users) != 1 { - t.Fatal("map was wrong length") - } - - found1 := false - found2 := false - for _, user := range users { - if user.Id == th.BasicUser.Id { - found1 = true - } else if user.Id == th.BasicUser2.Id { - found2 = true - } - } - - if found1 { - t.Fatal("should not have found profile") - } - if !found2 { - t.Fatal("should have found profile") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, TeamId: "junk", NotInChannelId: th.BasicChannel.Id}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - if len(users) != 0 { - t.Fatal("map was wrong length") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false }) - - privacyEmailPrefix := strings.ToLower(model.NewId()) - privacyUser := &model.User{Email: privacyEmailPrefix + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", FirstName: model.NewId(), LastName: "Jimmers"} - privacyUser = Client.Must(Client.CreateUser(privacyUser, "")).Data.(*model.User) - th.LinkUserToTeam(privacyUser, th.BasicTeam) - - if result, err := Client.SearchUsers(model.UserSearch{Term: privacyUser.FirstName}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == privacyUser.Id { - found = true - } - } - - if found { - t.Fatal("should not have found profile") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - - if result, err := Client.SearchUsers(model.UserSearch{Term: privacyUser.FirstName}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == privacyUser.Id { - found = true - } - } - - if found { - t.Fatal("should not have found profile") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = true }) - - if result, err := Client.SearchUsers(model.UserSearch{Term: privacyUser.FirstName}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == privacyUser.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: privacyEmailPrefix}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == privacyUser.Id { - found = true - } - } - - if found { - t.Fatal("should not have found profile") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = true }) - - if result, err := Client.SearchUsers(model.UserSearch{Term: privacyEmailPrefix}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == privacyUser.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - th.LoginBasic2() - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == th.BasicUser.Id { - found = true - } - } - - if !found { - t.Fatal("should have found profile") - } - } - - if _, err := Client.SearchUsers(model.UserSearch{}); err == nil { - t.Fatal("should have errored - blank term") - } - - if _, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, InChannelId: th.BasicChannel.Id}); err == nil { - t.Fatal("should not have access") - } - - if _, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, NotInChannelId: th.BasicChannel.Id}); err == nil { - t.Fatal("should not have access") - } - - userWithoutTeam := th.CreateUser(Client) - if result, err := Client.SearchUsers(model.UserSearch{Term: userWithoutTeam.Username}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == userWithoutTeam.Id { - found = true - } - } - - if !found { - t.Fatal("should have found user without team") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: userWithoutTeam.Username, WithoutTeam: true}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == userWithoutTeam.Id { - found = true - } - } - - if !found { - t.Fatal("should have found user without team") - } - } - - if result, err := Client.SearchUsers(model.UserSearch{Term: th.BasicUser.Username, WithoutTeam: true}); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - - found := false - for _, user := range users { - if user.Id == th.BasicUser.Id { - found = true - } - } - - if found { - t.Fatal("should not have found user with team") - } - } -} - -func TestAutocompleteUsers(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if result, err := Client.AutocompleteUsers(th.BasicUser.Username); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - if len(users) != 1 { - t.Fatal("should have returned 1 user in") - } - } - - if result, err := Client.AutocompleteUsers("amazonses"); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - if len(users) != 0 { - t.Fatal("should have returned 0 users - email should not autocomplete") - } - } - - if result, err := Client.AutocompleteUsers(""); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - if len(users) == 0 { - t.Fatal("should have many users") - } - } - - notInTeamUser := th.CreateUser(Client) - - if result, err := Client.AutocompleteUsers(notInTeamUser.Username); err != nil { - t.Fatal(err) - } else { - users := result.Data.([]*model.User) - if len(users) != 1 { - t.Fatal("should have returned 1 user in") - } - } - - if result, err := Client.AutocompleteUsersInTeam(notInTeamUser.Username); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInTeam) - if len(autocomplete.InTeam) != 0 { - t.Fatal("should have returned 0 users") - } - } - - if result, err := Client.AutocompleteUsersInTeam(th.BasicUser.Username); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInTeam) - if len(autocomplete.InTeam) != 1 { - t.Fatal("should have returned 1 user in") - } - } - - if result, err := Client.AutocompleteUsersInTeam(th.BasicUser.Username[0:5]); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInTeam) - if len(autocomplete.InTeam) < 1 { - t.Fatal("should have returned at least 1 user in") - } - } - - if result, err := Client.AutocompleteUsersInChannel(th.BasicUser.Username, th.BasicChannel.Id); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInChannel) - if len(autocomplete.InChannel) != 1 { - t.Fatal("should have returned 1 user in") - } - if len(autocomplete.OutOfChannel) != 0 { - t.Fatal("should have returned no users out") - } - } - - if result, err := Client.AutocompleteUsersInChannel("", th.BasicChannel.Id); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInChannel) - if len(autocomplete.InChannel) != 1 && autocomplete.InChannel[0].Id != th.BasicUser2.Id { - t.Fatal("should have returned at 1 user in") - } - if len(autocomplete.OutOfChannel) != 1 && autocomplete.OutOfChannel[0].Id != th.BasicUser2.Id { - t.Fatal("should have returned 1 user out") - } - } - - if result, err := Client.AutocompleteUsersInTeam(""); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInTeam) - if len(autocomplete.InTeam) != 2 { - t.Fatal("should have returned 2 users in") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false }) - - privacyUser := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1", FirstName: model.NewId(), LastName: "Jimmers"} - privacyUser = Client.Must(Client.CreateUser(privacyUser, "")).Data.(*model.User) - th.LinkUserToTeam(privacyUser, th.BasicTeam) - - if result, err := Client.AutocompleteUsersInChannel(privacyUser.FirstName, th.BasicChannel.Id); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInChannel) - if len(autocomplete.InChannel) != 0 { - t.Fatal("should have returned no users") - } - if len(autocomplete.OutOfChannel) != 0 { - t.Fatal("should have returned no users") - } - } - - if result, err := Client.AutocompleteUsersInTeam(privacyUser.FirstName); err != nil { - t.Fatal(err) - } else { - autocomplete := result.Data.(*model.UserAutocompleteInTeam) - if len(autocomplete.InTeam) != 0 { - t.Fatal("should have returned no users") - } - } - - if _, err := Client.AutocompleteUsersInChannel("", "junk"); err == nil { - t.Fatal("should have errored - bad channel id") - } - - Client.SetTeamId("junk") - if _, err := Client.AutocompleteUsersInChannel("", th.BasicChannel.Id); err == nil { - t.Fatal("should have errored - bad team id") - } - - if _, err := Client.AutocompleteUsersInTeam(""); err == nil { - t.Fatal("should have errored - bad team id") - } -} - -func TestGetByUsername(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if result, err := Client.GetByUsername(th.BasicUser.Username, ""); err != nil { - t.Fatal("Failed to get user") - } else { - if result.Data.(*model.User).Password != "" { - t.Fatal("User shouldn't have any password data once set") - } - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false }) - - if result, err := Client.GetByUsername(th.BasicUser2.Username, ""); err != nil { - t.Fatal(err) - } else { - u := result.Data.(*model.User) - if u.Password != "" { - t.Fatal("password must be empty") - } - if *u.AuthData != "" { - t.Fatal("auth data must be empty") - } - if u.Email != "" { - t.Fatal("email should be sanitized") - } - } - -} - -func TestGetByEmail(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - - if _, respMetdata := Client.GetByEmail(th.BasicUser.Email, ""); respMetdata.Error != nil { - t.Fatal("Failed to get user by email") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false }) - - if user, respMetdata := Client.GetByEmail(th.BasicUser2.Email, ""); respMetdata.Error != nil { - t.Fatal(respMetdata.Error) - } else { - if user.Password != "" { - t.Fatal("password must be empty") - } - if *user.AuthData != "" { - t.Fatal("auth data must be empty") - } - if user.Email != "" { - t.Fatal("email should be sanitized") - } - } -} diff --git a/api/webhook.go b/api/webhook.go deleted file mode 100644 index e3e12816a..000000000 --- a/api/webhook.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" - - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitWebhook() { - api.BaseRoutes.Hooks.Handle("/incoming/create", api.ApiUserRequired(createIncomingHook)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/incoming/update", api.ApiUserRequired(updateIncomingHook)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/incoming/delete", api.ApiUserRequired(deleteIncomingHook)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/incoming/list", api.ApiUserRequired(getIncomingHooks)).Methods("GET") - - api.BaseRoutes.Hooks.Handle("/outgoing/create", api.ApiUserRequired(createOutgoingHook)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/outgoing/update", api.ApiUserRequired(updateOutgoingHook)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/outgoing/regen_token", api.ApiUserRequired(regenOutgoingHookToken)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/outgoing/delete", api.ApiUserRequired(deleteOutgoingHook)).Methods("POST") - api.BaseRoutes.Hooks.Handle("/outgoing/list", api.ApiUserRequired(getOutgoingHooks)).Methods("GET") -} - -func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { - hook := model.IncomingWebhookFromJson(r.Body) - if hook == nil { - c.SetInvalidParam("createIncomingHook", "webhook") - return - } - - channel, err := c.App.GetChannel(hook.ChannelId) - if err != nil { - c.Err = err - return - } - - c.LogAudit("attempt") - - if !c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if channel.Type != model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_READ_CHANNEL) { - c.LogAudit("fail - bad channel permissions") - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - if incomingHook, err := c.App.CreateIncomingWebhookForChannel(c.Session.UserId, channel, hook); err != nil { - c.Err = err - return - } else { - c.LogAudit("success") - w.Write([]byte(incomingHook.ToJson())) - } -} - -func updateIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { - - hook := model.IncomingWebhookFromJson(r.Body) - - if hook == nil { - c.SetInvalidParam("updateIncomingHook", "webhook") - return - } - - c.LogAudit("attempt") - - oldHook, err := c.App.GetIncomingWebhook(hook.Id) - if err != nil { - c.Err = err - return - } - - if c.TeamId != oldHook.TeamId { - c.Err = model.NewAppError("updateIncomingHook", "api.webhook.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, oldHook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if c.Session.UserId != oldHook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, oldHook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) - return - } - - channel, err := c.App.GetChannel(hook.ChannelId) - if err != nil { - c.Err = err - return - } - - if channel.Type != model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_READ_CHANNEL) { - c.LogAudit("fail - bad channel permissions") - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) - return - } - - rhook, err := c.App.UpdateIncomingWebhook(oldHook, hook) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - w.Write([]byte(rhook.ToJson())) -} - -func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteIncomingHook", "id") - return - } - - hook, err := c.App.GetIncomingWebhook(id) - if err != nil { - c.Err = err - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - c.LogAudit("attempt") - - if c.Session.UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) - return - } - - if err := c.App.DeleteIncomingWebhook(id); err != nil { - c.LogAudit("fail") - c.Err = err - return - } - - c.LogAudit("success") - w.Write([]byte(model.MapToJson(props))) -} - -func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if hooks, err := c.App.GetIncomingWebhooksForTeamPage(c.TeamId, 0, 100); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.IncomingWebhookListToJson(hooks))) - } -} - -func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { - hook := model.OutgoingWebhookFromJson(r.Body) - if hook == nil { - c.SetInvalidParam("createOutgoingHook", "webhook") - return - } - - c.LogAudit("attempt") - - hook.TeamId = c.TeamId - hook.CreatorId = c.Session.UserId - - if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if rhook, err := c.App.CreateOutgoingWebhook(hook); err != nil { - c.LogAudit("fail") - c.Err = err - return - } else { - c.LogAudit("success") - w.Write([]byte(rhook.ToJson())) - } -} - -func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if hooks, err := c.App.GetOutgoingWebhooksForTeamPage(c.TeamId, 0, 100); err != nil { - c.Err = err - return - } else { - w.Write([]byte(model.OutgoingWebhookListToJson(hooks))) - } -} - -func updateOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { - c.LogAudit("attempt") - - hook := model.OutgoingWebhookFromJson(r.Body) - - if hook == nil { - c.SetInvalidParam("updateOutgoingHook", "webhook") - return - } - - oldHook, err := c.App.GetOutgoingWebhook(hook.Id) - if err != nil { - c.Err = err - return - } - - if c.TeamId != oldHook.TeamId { - c.Err = model.NewAppError("updateOutgoingHook", "api.webhook.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusForbidden) - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, oldHook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if c.Session.UserId != oldHook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, oldHook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) - return - } - - rhook, err := c.App.UpdateOutgoingWebhook(oldHook, hook) - if err != nil { - c.Err = err - return - } - - c.LogAudit("success") - w.Write([]byte(rhook.ToJson())) -} - -func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteIncomingHook", "id") - return - } - - c.LogAudit("attempt") - - if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - hook, err := c.App.GetOutgoingWebhook(id) - if err != nil { - c.Err = err - return - } - - if c.Session.UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) - return - } - - if err := c.App.DeleteOutgoingWebhook(id); err != nil { - c.LogAudit("fail") - c.Err = err - return - } - - c.LogAudit("success") - w.Write([]byte(model.MapToJson(props))) -} - -func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("regenOutgoingHookToken", "id") - return - } - - hook, err := c.App.GetOutgoingWebhook(id) - if err != nil { - c.Err = err - return - } - - c.LogAudit("attempt") - - if c.TeamId != hook.TeamId { - c.Err = model.NewAppError("regenOutgoingHookToken", "api.webhook.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusForbidden) - return - } - - if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) { - c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) - return - } - - if c.Session.UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { - c.LogAudit("fail - inappropriate permissions") - c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) - return - } - - if rhook, err := c.App.RegenOutgoingWebhookToken(hook); err != nil { - c.Err = err - return - } else { - w.Write([]byte(rhook.ToJson())) - } -} diff --git a/api/webhook_test.go b/api/webhook_test.go deleted file mode 100644 index c9ca7d783..000000000 --- a/api/webhook_test.go +++ /dev/null @@ -1,968 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" -) - -func TestCreateIncomingHook(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - user := th.SystemAdminUser - team := th.SystemAdminTeam - channel1 := th.CreateChannel(Client, team) - channel2 := th.CreatePrivateChannel(Client, team) - channel3 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook := &model.IncomingWebhook{ChannelId: channel1.Id} - - var rhook *model.IncomingWebhook - if result, err := Client.CreateIncomingWebhook(hook); err != nil { - t.Fatal(err) - } else { - rhook = result.Data.(*model.IncomingWebhook) - } - - if hook.ChannelId != rhook.ChannelId { - t.Fatal("channel ids didn't match") - } - - if rhook.UserId != user.Id { - t.Fatal("user ids didn't match") - } - - if rhook.TeamId != team.Id { - t.Fatal("team ids didn't match") - } - - hook = &model.IncomingWebhook{ChannelId: "junk"} - if _, err := Client.CreateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - bad channel id") - } - - hook = &model.IncomingWebhook{ChannelId: channel2.Id, UserId: "123", TeamId: "456"} - if result, err := Client.CreateIncomingWebhook(hook); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.IncomingWebhook).UserId != user.Id { - t.Fatal("bad user id wasn't overwritten") - } - if result.Data.(*model.IncomingWebhook).TeamId != team.Id { - t.Fatal("bad team id wasn't overwritten") - } - } - - Client.Must(Client.LeaveChannel(channel3.Id)) - - hook = &model.IncomingWebhook{ChannelId: channel3.Id, UserId: user.Id, TeamId: team.Id} - if _, err := Client.CreateIncomingWebhook(hook); err != nil { - t.Fatal(err) - } - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - hook = &model.IncomingWebhook{ChannelId: channel1.Id} - - if _, err := Client.CreateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - not system/team admin") - } - - Client.Logout() - th.UpdateUserToTeamAdmin(user2, team) - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - if _, err := Client.CreateIncomingWebhook(hook); err != nil { - t.Fatal(err) - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - if _, err := Client.CreateIncomingWebhook(hook); err != nil { - t.Fatal(err) - } - - hook = &model.IncomingWebhook{ChannelId: channel2.Id} - - if _, err := Client.CreateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - channel is private and not a member") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = false }) - - if _, err := Client.CreateIncomingWebhook(hook); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} - -func TestUpdateIncomingHook(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - - channel1 := th.CreateChannel(Client, team) - channel2 := th.CreatePrivateChannel(Client, team) - channel3 := th.CreateChannel(Client, team) - - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - team2 := th.CreateTeam(Client) - user3 := th.CreateUser(Client) - th.LinkUserToTeam(user3, team2) - th.UpdateUserToTeamAdmin(user3, team2) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - hook := createIncomingWebhook(channel1.Id, Client, t) - - t.Run("UpdateIncomingHook", func(t *testing.T) { - hook.DisplayName = "hook2" - hook.Description = "description" - hook.ChannelId = channel3.Id - - if result, err := Client.UpdateIncomingWebhook(hook); err != nil { - t.Fatal("Update hook should not fail") - } else { - updatedHook := result.Data.(*model.IncomingWebhook) - - if updatedHook.DisplayName != "hook2" { - t.Fatal("Hook name is not updated") - } - - if updatedHook.Description != "description" { - t.Fatal("Hook description is not updated") - } - - if updatedHook.ChannelId != channel3.Id { - t.Fatal("Hook channel is not updated") - } - } - }) - - t.Run("RetainCreateAt", func(t *testing.T) { - hook2 := &model.IncomingWebhook{ChannelId: channel1.Id, CreateAt: 100} - - if result, err := Client.CreateIncomingWebhook(hook2); err != nil { - t.Fatal("hook creation failed") - } else { - createdHook := result.Data.(*model.IncomingWebhook) - createdHook.DisplayName = "Name2" - - if result, err := Client.UpdateIncomingWebhook(createdHook); err != nil { - t.Fatal("Update hook should not fail") - } else { - updatedHook := result.Data.(*model.IncomingWebhook) - - if updatedHook.CreateAt != createdHook.CreateAt { - t.Fatal("failed - hook create at should not be changed") - } - } - } - }) - - t.Run("ModifyUpdateAt", func(t *testing.T) { - hook.DisplayName = "Name3" - - if result, err := Client.UpdateIncomingWebhook(hook); err != nil { - t.Fatal("Update hook should not fail") - } else { - updatedHook := result.Data.(*model.IncomingWebhook) - - if updatedHook.UpdateAt == hook.UpdateAt { - t.Fatal("failed - hook updateAt is not updated") - } - } - }) - - t.Run("UpdateNonExistentHook", func(t *testing.T) { - nonExistentHook := &model.IncomingWebhook{ChannelId: channel1.Id} - - if _, err := Client.UpdateIncomingWebhook(nonExistentHook); err == nil { - t.Fatal("should have failed - update a non-existent hook") - } - }) - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - t.Run("UserIsNotAdminOfTeam", func(t *testing.T) { - if _, err := Client.UpdateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - user is not admin of team") - } - }) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - - t.Run("OnlyAdminIntegrationsDisabled", func(t *testing.T) { - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - t.Run("UpdateHookOfSameUser", func(t *testing.T) { - sameUserHook := &model.IncomingWebhook{ChannelId: channel1.Id, UserId: user2.Id} - if result, err := Client.CreateIncomingWebhook(sameUserHook); err != nil { - t.Fatal("Hook creation failed") - } else { - sameUserHook = result.Data.(*model.IncomingWebhook) - } - - if _, err := Client.UpdateIncomingWebhook(sameUserHook); err != nil { - t.Fatal("should not fail - only admin integrations are disabled & hook of same user") - } - }) - - t.Run("UpdateHookOfDifferentUser", func(t *testing.T) { - if _, err := Client.UpdateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - user does not have permissions to update other user's hooks") - } - }) - }) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - Client.Logout() - th.UpdateUserToTeamAdmin(user2, team) - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - t.Run("UpdateByDifferentUser", func(t *testing.T) { - if result, err := Client.UpdateIncomingWebhook(hook); err != nil { - t.Fatal("Update hook should not fail") - } else { - updatedHook := result.Data.(*model.IncomingWebhook) - - if updatedHook.UserId == user2.Id { - t.Fatal("Hook's creator userId is not retained") - } - } - }) - - t.Run("IncomingHooksDisabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = false }) - if _, err := Client.UpdateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - incoming hooks are disabled") - } - }) - - t.Run("PrivateChannel", func(t *testing.T) { - hook.ChannelId = channel2.Id - - if _, err := Client.UpdateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - updating to a private channel where the user is not a member") - } - }) - - t.Run("UpdateToNonExistentChannel", func(t *testing.T) { - hook.ChannelId = "junk" - if _, err := Client.UpdateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - bad channel id") - } - }) - - Client.Logout() - Client.Must(Client.LoginById(user3.Id, user3.Password)) - Client.SetTeamId(team2.Id) - t.Run("UpdateToADifferentTeam", func(t *testing.T) { - if _, err := Client.UpdateIncomingWebhook(hook); err == nil { - t.Fatal("should have failed - update to a different team is not allowed") - } - }) -} - -func createIncomingWebhook(channelID string, Client *model.Client, t *testing.T) *model.IncomingWebhook { - hook := &model.IncomingWebhook{ChannelId: channelID} - if result, err := Client.CreateIncomingWebhook(hook); err != nil { - t.Fatal("Hook creation failed") - } else { - hook = result.Data.(*model.IncomingWebhook) - } - - return hook -} - -func createOutgoingWebhook(channelID string, callbackURLs []string, triggerWords []string, Client *model.Client, t *testing.T) *model.OutgoingWebhook { - hook := &model.OutgoingWebhook{ChannelId: channelID, CallbackURLs: callbackURLs, TriggerWords: triggerWords} - if result, err := Client.CreateOutgoingWebhook(hook); err != nil { - t.Fatal("Hook creation failed") - } else { - hook = result.Data.(*model.OutgoingWebhook) - } - - return hook -} - -func TestListIncomingHooks(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - channel1 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook1 := &model.IncomingWebhook{ChannelId: channel1.Id} - hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook) - - hook2 := &model.IncomingWebhook{ChannelId: channel1.Id} - hook2 = Client.Must(Client.CreateIncomingWebhook(hook2)).Data.(*model.IncomingWebhook) - - if result, err := Client.ListIncomingWebhooks(); err != nil { - t.Fatal(err) - } else { - hooks := result.Data.([]*model.IncomingWebhook) - - if len(hooks) != 2 { - t.Fatal("incorrect number of hooks") - } - } - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - if _, err := Client.ListIncomingWebhooks(); err == nil { - t.Fatal("should have errored - not system/team admin") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - if _, err := Client.ListIncomingWebhooks(); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = false }) - - if _, err := Client.ListIncomingWebhooks(); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} - -func TestDeleteIncomingHook(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - channel1 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook := &model.IncomingWebhook{ChannelId: channel1.Id} - hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook) - - if _, err := Client.DeleteIncomingWebhook(hook.Id); err != nil { - t.Fatal(err) - } - - if _, err := Client.DeleteIncomingWebhook("junk"); err == nil { - t.Fatal("should have failed - bad id") - } - - if _, err := Client.DeleteIncomingWebhook(""); err == nil { - t.Fatal("should have failed - empty id") - } - - hooks := Client.Must(Client.ListIncomingWebhooks()).Data.([]*model.IncomingWebhook) - if len(hooks) != 0 { - t.Fatal("delete didn't work properly") - } - - hook = &model.IncomingWebhook{ChannelId: channel1.Id} - hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook) - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil { - t.Fatal("should have failed - not system/team admin") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil { - t.Fatal("should have failed - not creator or team admin") - } - - hook = &model.IncomingWebhook{ChannelId: channel1.Id} - hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook) - - if _, err := Client.DeleteIncomingWebhook(hook.Id); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = false }) - - if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} - -func TestCreateOutgoingHook(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - user := th.SystemAdminUser - team := th.SystemAdminTeam - team2 := th.CreateTeam(Client) - channel1 := th.CreateChannel(Client, team) - channel2 := th.CreatePrivateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - user3 := th.CreateUser(Client) - th.LinkUserToTeam(user3, team2) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - - var rhook *model.OutgoingWebhook - if result, err := Client.CreateOutgoingWebhook(hook); err != nil { - t.Fatal(err) - } else { - rhook = result.Data.(*model.OutgoingWebhook) - } - - if hook.ChannelId != rhook.ChannelId { - t.Fatal("channel ids didn't match") - } - - if rhook.CreatorId != user.Id { - t.Fatal("user ids didn't match") - } - - if rhook.TeamId != team.Id { - t.Fatal("team ids didn't match") - } - - hook = &model.OutgoingWebhook{ChannelId: channel1.Id, TriggerWords: []string{"cats", "dogs"}, CallbackURLs: []string{"http://nowhere.com", "http://cats.com"}} - hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, TriggerWords: []string{"cats"}, CallbackURLs: []string{"http://nowhere.com"}} - - if _, err := Client.CreateOutgoingWebhook(hook); err != nil { - t.Fatal("multiple trigger words and urls failed") - } - - if _, err := Client.CreateOutgoingWebhook(hook1); err == nil { - t.Fatal("should have failed - duplicate trigger words and urls") - } - - hook = &model.OutgoingWebhook{ChannelId: "junk", CallbackURLs: []string{"http://nowhere.com"}} - if _, err := Client.CreateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - bad channel id") - } - - hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CreatorId: "123", TeamId: "456", CallbackURLs: []string{"http://nowhere.com"}} - if result, err := Client.CreateOutgoingWebhook(hook); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.OutgoingWebhook).CreatorId != user.Id { - t.Fatal("bad user id wasn't overwritten") - } - if result.Data.(*model.OutgoingWebhook).TeamId != team.Id { - t.Fatal("bad team id wasn't overwritten") - } - } - - hook = &model.OutgoingWebhook{ChannelId: channel2.Id, CallbackURLs: []string{"http://nowhere.com"}} - if _, err := Client.CreateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - private channel") - } - - hook = &model.OutgoingWebhook{CallbackURLs: []string{"http://nowhere.com"}} - if _, err := Client.CreateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - blank channel and trigger words") - } - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - if _, err := Client.CreateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - not system/team admin") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - if _, err := Client.CreateOutgoingWebhook(hook); err != nil { - t.Fatal(err) - } - - Client.Logout() - Client.Must(Client.LoginById(user3.Id, user3.Password)) - Client.SetTeamId(team2.Id) - - if _, err := Client.CreateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - wrong team") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = false }) - - if _, err := Client.CreateOutgoingWebhook(hook); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} - -func TestListOutgoingHooks(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - channel1 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - hook1 = Client.Must(Client.CreateOutgoingWebhook(hook1)).Data.(*model.OutgoingWebhook) - - hook2 := &model.OutgoingWebhook{TriggerWords: []string{"trigger"}, CallbackURLs: []string{"http://nowhere.com"}} - hook2 = Client.Must(Client.CreateOutgoingWebhook(hook2)).Data.(*model.OutgoingWebhook) - - if result, err := Client.ListOutgoingWebhooks(); err != nil { - t.Fatal(err) - } else { - hooks := result.Data.([]*model.OutgoingWebhook) - - if len(hooks) != 2 { - t.Fatal("incorrect number of hooks") - } - } - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - if _, err := Client.ListOutgoingWebhooks(); err == nil { - t.Fatal("should have failed - not system/team admin") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - if _, err := Client.ListOutgoingWebhooks(); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = false }) - - if _, err := Client.ListOutgoingWebhooks(); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} - -func TestUpdateOutgoingHook(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - user := th.SystemAdminUser - team := th.SystemAdminTeam - team2 := th.CreateTeam(Client) - channel1 := th.CreateChannel(Client, team) - channel2 := th.CreatePrivateChannel(Client, team) - channel3 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - user3 := th.CreateUser(Client) - th.LinkUserToTeam(user3, team2) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook := createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"cats"}, Client, t) - createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"dogs"}, Client, t) - - hook.DisplayName = "Cats" - hook.Description = "Get me some cats" - t.Run("OutgoingHooksDisabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = false }) - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - outgoing webhooks disabled") - } - }) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - t.Run("UpdateOutgoingWebhook", func(t *testing.T) { - if result, err := Client.UpdateOutgoingWebhook(hook); err != nil { - t.Fatal("failed to update outgoing web hook") - } else { - updatedHook := result.Data.(*model.OutgoingWebhook) - - if updatedHook.DisplayName != hook.DisplayName { - t.Fatal("Hook display name did not get updated") - } - - if updatedHook.Description != hook.Description { - t.Fatal("Hook description did not get updated") - } - } - }) - - t.Run("RetainCreateAt", func(t *testing.T) { - hook2 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}, TriggerWords: []string{"rats"}} - - if result, err := Client.CreateOutgoingWebhook(hook2); err != nil { - t.Fatal("hook creation failed") - } else { - createdHook := result.Data.(*model.OutgoingWebhook) - createdHook.DisplayName = "Name2" - - if result, err := Client.UpdateOutgoingWebhook(createdHook); err != nil { - t.Fatal("Update hook should not fail") - } else { - updatedHook := result.Data.(*model.OutgoingWebhook) - - if updatedHook.CreateAt != createdHook.CreateAt { - t.Fatal("failed - hook create at should not be changed") - } - } - } - }) - - t.Run("ModifyUpdateAt", func(t *testing.T) { - hook.DisplayName = "Name3" - - if result, err := Client.UpdateOutgoingWebhook(hook); err != nil { - t.Fatal("Update hook should not fail") - } else { - updatedHook := result.Data.(*model.OutgoingWebhook) - - if updatedHook.UpdateAt == hook.UpdateAt { - t.Fatal("failed - hook updateAt is not updated") - } - } - }) - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - user does not have permissions to manage webhooks") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook2 := createOutgoingWebhook(channel1.Id, []string{"http://nowhereelse.com"}, []string{"dogs"}, Client, t) - - if _, err := Client.UpdateOutgoingWebhook(hook2); err != nil { - t.Fatal("update webhook failed when admin only integrations is turned off") - } - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - Client.Logout() - th.LinkUserToTeam(user3, team) - th.UpdateUserToTeamAdmin(user3, team) - Client.Must(Client.LoginById(user3.Id, user3.Password)) - Client.SetTeamId(team.Id) - t.Run("RetainHookCreator", func(t *testing.T) { - if result, err := Client.UpdateOutgoingWebhook(hook); err != nil { - t.Fatal("failed to update outgoing web hook") - } else { - updatedHook := result.Data.(*model.OutgoingWebhook) - - if updatedHook.CreatorId != user.Id { - t.Fatal("hook creator should not be changed") - } - } - }) - - Client.Logout() - Client.Must(Client.LoginById(user.Id, user.Password)) - Client.SetTeamId(team.Id) - t.Run("UpdateToExistingTriggerWordAndCallback", func(t *testing.T) { - t.Run("OnSameChannel", func(t *testing.T) { - hook.TriggerWords = []string{"dogs"} - - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - duplicate trigger words & channel urls") - } - }) - - t.Run("OnDifferentChannel", func(t *testing.T) { - hook.TriggerWords = []string{"dogs"} - hook.ChannelId = channel3.Id - - if _, err := Client.UpdateOutgoingWebhook(hook); err != nil { - t.Fatal("update of hook failed with duplicate trigger word but different channel") - } - }) - }) - - t.Run("UpdateToNonExistentChannel", func(t *testing.T) { - hook.ChannelId = "junk" - - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - non existent channel") - } - }) - - t.Run("UpdateToPrivateChannel", func(t *testing.T) { - hook.ChannelId = channel2.Id - - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - update to a private channel") - } - }) - - t.Run("UpdateToBlankTriggerWordAndChannel", func(t *testing.T) { - hook.ChannelId = "" - hook.TriggerWords = nil - - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - update to blank trigger words & channel") - } - }) - - Client.Logout() - Client.Must(Client.LoginById(user3.Id, user3.Password)) - Client.SetTeamId(team2.Id) - t.Run("UpdateToADifferentTeam", func(t *testing.T) { - if _, err := Client.UpdateOutgoingWebhook(hook); err == nil { - t.Fatal("should have failed - update to a different team is not allowed") - } - }) -} - -func TestDeleteOutgoingHook(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - channel1 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) - - if _, err := Client.DeleteOutgoingWebhook("junk"); err == nil { - t.Fatal("should have failed - bad hook id") - } - - if _, err := Client.DeleteOutgoingWebhook(""); err == nil { - t.Fatal("should have failed - empty hook id") - } - - if _, err := Client.DeleteOutgoingWebhook(hook.Id); err != nil { - t.Fatal(err) - } - - hooks := Client.Must(Client.ListOutgoingWebhooks()).Data.([]*model.OutgoingWebhook) - if len(hooks) != 0 { - t.Fatal("delete didn't work properly") - } - - hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil { - t.Fatal("should have failed - not system/team admin") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil { - t.Fatal("should have failed - not creator or team admin") - } - - hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) - - if _, err := Client.DeleteOutgoingWebhook(hook.Id); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = false }) - - if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} - -func TestRegenOutgoingHookToken(t *testing.T) { - th := Setup().InitSystemAdmin() - defer th.TearDown() - - Client := th.SystemAdminClient - team := th.SystemAdminTeam - team2 := th.CreateTeam(Client) - channel1 := th.CreateChannel(Client, team) - user2 := th.CreateUser(Client) - th.LinkUserToTeam(user2, team) - user3 := th.CreateUser(Client) - th.LinkUserToTeam(user3, team2) - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - - defaultRolePermissions := th.SaveDefaultRolePermissions() - defer func() { - th.RestoreDefaultRolePermissions(defaultRolePermissions) - }() - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) - - // Revoke permission from regular users. - th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) - - if _, err := Client.RegenOutgoingWebhookToken("junk"); err == nil { - t.Fatal("should have failed - bad id") - } - - if _, err := Client.RegenOutgoingWebhookToken(""); err == nil { - t.Fatal("should have failed - empty id") - } - - if result, err := Client.RegenOutgoingWebhookToken(hook.Id); err != nil { - t.Fatal(err) - } else { - if result.Data.(*model.OutgoingWebhook).Token == hook.Token { - t.Fatal("regen didn't work properly") - } - } - - Client.SetTeamId(model.NewId()) - if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil { - t.Fatal("should have failed - wrong team id") - } - - Client.Logout() - Client.Must(Client.LoginById(user2.Id, user2.Password)) - Client.SetTeamId(team.Id) - - if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil { - t.Fatal("should have failed - not system/team admin") - } - - // Grant permission to regular users. - th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) - - hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} - hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) - - if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err != nil { - t.Fatal(err) - } - - Client.Logout() - Client.Must(Client.LoginById(user3.Id, user3.Password)) - Client.SetTeamId(team2.Id) - - if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil { - t.Fatal("should have failed - wrong team") - } - - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = false }) - - if _, err := Client.RegenOutgoingWebhookToken(hook.Id); err == nil { - t.Fatal("should have errored - webhooks turned off") - } -} diff --git a/api/webrtc.go b/api/webrtc.go deleted file mode 100644 index cd37c0ad8..000000000 --- a/api/webrtc.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "net/http" -) - -func (api *API) InitWebrtc() { - api.BaseRoutes.Webrtc.Handle("/token", api.ApiUserRequired(webrtcToken)).Methods("POST") -} - -func webrtcToken(c *Context, w http.ResponseWriter, r *http.Request) { - result, err := c.App.GetWebrtcInfoForSession(c.Session.Id) - - if err != nil { - c.Err = err - return - } - - w.Write([]byte(result.ToJson())) -} diff --git a/api/websocket.go b/api/websocket.go deleted file mode 100644 index 7f2c9c0db..000000000 --- a/api/websocket.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - "net/http" - - "github.com/gorilla/websocket" - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" -) - -func (api *API) InitWebSocket() { - api.BaseRoutes.Users.Handle("/websocket", api.ApiAppHandlerTrustRequester(connect)).Methods("GET") -} - -func connect(c *Context, w http.ResponseWriter, r *http.Request) { - upgrader := websocket.Upgrader{ - ReadBufferSize: model.SOCKET_MAX_MESSAGE_SIZE_KB, - WriteBufferSize: model.SOCKET_MAX_MESSAGE_SIZE_KB, - CheckOrigin: c.App.OriginChecker(), - } - - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - mlog.Error(fmt.Sprintf("websocket connect err: %v", err)) - c.Err = model.NewAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "", http.StatusInternalServerError) - return - } - - wc := c.App.NewWebConn(ws, c.Session, c.T, c.Locale) - - if len(c.Session.UserId) > 0 { - c.App.HubRegister(wc) - } - - wc.Pump() -} diff --git a/api/websocket_test.go b/api/websocket_test.go deleted file mode 100644 index a3c716abd..000000000 --- a/api/websocket_test.go +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "fmt" - //"encoding/json" - //"net/http" - "net/http" - "testing" - "time" - - "github.com/gorilla/websocket" - "github.com/mattermost/mattermost-server/model" -) - -/*func TestWebSocketAuthentication(t *testing.T) { - th := Setup().InitBasic() - WebSocketClient, err := th.CreateWebSocketClient() - if err != nil { - t.Fatal(err) - } - WebSocketClient.Listen() - - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { - t.Fatal("should have responded OK to authentication challenge") - } - - WebSocketClient.SendMessage("ping", nil) - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Data["text"].(string) != "pong" { - t.Fatal("wrong response") - } - - WebSocketClient.Close() - - authToken := WebSocketClient.AuthToken - WebSocketClient.AuthToken = "junk" - if err := WebSocketClient.Connect(); err != nil { - t.Fatal(err) - } - WebSocketClient.Listen() - - if resp := <-WebSocketClient.ResponseChannel; resp != nil { - t.Fatal("should have closed") - } - - if conn, _, err := websocket.DefaultDialer.Dial(WebSocketClient.ApiUrl+"/users/websocket", nil); err != nil { - t.Fatal("should have connected") - } else { - req := &model.WebSocketRequest{} - req.Seq = 1 - req.Action = "ping" - conn.WriteJSON(req) - - closedAutomatically := false - hitNotAuthedError := false - - go func() { - time.Sleep(10 * time.Second) - conn.Close() - - if !closedAutomatically { - t.Fatal("should have closed automatically in 5 seconds") - } - }() - - for { - if _, rawMsg, err := conn.ReadMessage(); err != nil { - closedAutomatically = true - conn.Close() - break - } else { - var response model.WebSocketResponse - if err := json.Unmarshal(rawMsg, &response); err != nil && !response.IsValid() { - t.Fatal("should not have failed") - } else { - if response.Error == nil || response.Error.Id != "api.web_socket_router.not_authenticated.app_error" { - t.Log(response.Error.Id) - t.Fatal("wrong error") - continue - } - - hitNotAuthedError = true - } - } - } - - if !hitNotAuthedError { - t.Fatal("should have received a not authenticated response") - } - } - - header := http.Header{} - header.Set(model.HEADER_AUTH, "BEARER "+authToken) - if conn, _, err := websocket.DefaultDialer.Dial(WebSocketClient.ApiUrl+"/users/websocket", header); err != nil { - t.Fatal("should have connected") - } else { - if _, rawMsg, err := conn.ReadMessage(); err != nil { - t.Fatal("should not have closed automatically") - } else { - var event model.WebSocketEvent - if err := json.Unmarshal(rawMsg, &event); err != nil && !event.IsValid() { - t.Fatal("should not have failed") - } else if event.Event != model.WEBSOCKET_EVENT_HELLO { - t.Log(event.ToJson()) - t.Fatal("should have helloed") - } - } - - conn.Close() - } -}*/ - -func TestWebSocket(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - WebSocketClient, err := th.CreateWebSocketClient() - if err != nil { - t.Fatal(err) - } - defer WebSocketClient.Close() - - time.Sleep(300 * time.Millisecond) - - // Test closing and reconnecting - WebSocketClient.Close() - if err := WebSocketClient.Connect(); err != nil { - t.Fatal(err) - } - - WebSocketClient.Listen() - - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { - t.Fatal("should have responded OK to authentication challenge") - } - - WebSocketClient.SendMessage("ping", nil) - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Data["text"].(string) != "pong" { - t.Fatal("wrong response") - } - - WebSocketClient.SendMessage("", nil) - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.no_action.app_error" { - t.Fatal("should have been no action response") - } - - WebSocketClient.SendMessage("junk", nil) - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.bad_action.app_error" { - t.Fatal("should have been bad action response") - } - - req := &model.WebSocketRequest{} - req.Seq = 0 - req.Action = "ping" - WebSocketClient.Conn.WriteJSON(req) - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.bad_seq.app_error" { - t.Fatal("should have been bad action response") - } - - WebSocketClient.UserTyping("", "") - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.websocket_handler.invalid_param.app_error" { - t.Fatal("should have been invalid param response") - } else { - if resp.Error.DetailedError != "" { - t.Fatal("detailed error not cleared") - } - } -} - -func TestWebSocketEvent(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - WebSocketClient, err := th.CreateWebSocketClient() - if err != nil { - t.Fatal(err) - } - defer WebSocketClient.Close() - - WebSocketClient.Listen() - - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { - t.Fatal("should have responded OK to authentication challenge") - } - - omitUser := make(map[string]bool, 1) - omitUser["somerandomid"] = true - evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser) - evt1.Add("user_id", "somerandomid") - th.App.Publish(evt1) - - time.Sleep(300 * time.Millisecond) - - stop := make(chan bool) - eventHit := false - - go func() { - for { - select { - case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" { - eventHit = true - } - case <-stop: - return - } - } - }() - - time.Sleep(400 * time.Millisecond) - - stop <- true - - if !eventHit { - t.Fatal("did not receive typing event") - } - - evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil) - th.App.Publish(evt2) - time.Sleep(300 * time.Millisecond) - - eventHit = false - - go func() { - for { - select { - case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_TYPING { - eventHit = true - } - case <-stop: - return - } - } - }() - - time.Sleep(400 * time.Millisecond) - - stop <- true - - if eventHit { - t.Fatal("got typing event for bad channel id") - } -} - -func TestCreateDirectChannelWithSocket(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - Client := th.BasicClient - user2 := th.BasicUser2 - - users := make([]*model.User, 0) - users = append(users, user2) - - for i := 0; i < 10; i++ { - users = append(users, th.CreateUser(Client)) - } - - WebSocketClient, err := th.CreateWebSocketClient() - if err != nil { - t.Fatal(err) - } - defer WebSocketClient.Close() - WebSocketClient.Listen() - - time.Sleep(300 * time.Millisecond) - if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { - t.Fatal("should have responded OK to authentication challenge") - } - - wsr := <-WebSocketClient.EventChannel - if wsr.Event != model.WEBSOCKET_EVENT_HELLO { - t.Fatal("missing hello") - } - - stop := make(chan bool) - count := 0 - - go func() { - for { - select { - case wsr := <-WebSocketClient.EventChannel: - if wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED { - count = count + 1 - } - - case <-stop: - return - } - } - }() - - for _, user := range users { - time.Sleep(100 * time.Millisecond) - if _, err := Client.CreateDirectChannel(user.Id); err != nil { - t.Fatal("failed to create DM channel") - } - } - - time.Sleep(5000 * time.Millisecond) - - stop <- true - - if count != len(users) { - t.Fatal("We didn't get the proper amount of direct_added messages") - } - -} - -func TestWebsocketOriginSecurity(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - url := fmt.Sprintf("ws://localhost:%v", th.App.Srv.ListenAddr.Port) - - // Should fail because origin doesn't match - _, _, err := websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{ - "Origin": []string{"http://www.evil.com"}, - }) - if err == nil { - t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") - } - - // We are not a browser so we can spoof this just fine - _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{ - "Origin": []string{fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port)}, - }) - if err != nil { - t.Fatal(err) - } - - // Should succeed now because open CORS - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "*" }) - _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{ - "Origin": []string{"http://www.evil.com"}, - }) - if err != nil { - t.Fatal(err) - } - - // Should succeed now because matching CORS - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.evil.com" }) - _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{ - "Origin": []string{"http://www.evil.com"}, - }) - if err != nil { - t.Fatal(err) - } - - // Should fail because non-matching CORS - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) - _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{ - "Origin": []string{"http://www.evil.com"}, - }) - if err == nil { - t.Fatal("Should have errored because Origin contain AllowCorsFrom") - } - - // Should fail because non-matching CORS - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) - _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{ - "Origin": []string{"http://www.good.co"}, - }) - if err == nil { - t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "" }) -} diff --git a/api4/api.go b/api4/api.go index 9172391dd..918154c0d 100644 --- a/api4/api.go +++ b/api4/api.go @@ -113,7 +113,7 @@ type API struct { BaseRoutes *Routes } -func Init(a *app.App, root *mux.Router, full bool) *API { +func Init(a *app.App, root *mux.Router) *API { api := &API{ App: a, BaseRoutes: &Routes{}, @@ -231,10 +231,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404)) - // REMOVE CONDITION WHEN APIv3 REMOVED - if full { - a.InitEmailBatching() - } + a.InitEmailBatching() return api } diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 0ce334154..86150e05a 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -120,7 +120,7 @@ func setupTestHelper(enterprise bool) *TestHelper { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) - Init(th.App, th.App.Srv.Router, true) + Init(th.App, th.App.Srv.Router) web.NewWeb(th.App, th.App.Srv.Router) wsapi.Init(th.App, th.App.Srv.WebSocketRouter) th.App.Srv.Store.MarkSystemRanUnitTests() diff --git a/api4/commands_test.go b/api4/commands_test.go new file mode 100644 index 000000000..cb960c608 --- /dev/null +++ b/api4/commands_test.go @@ -0,0 +1,449 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "strings" + "testing" + "time" + + "github.com/mattermost/mattermost-server/model" +) + +func TestEchoCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel1 := th.BasicChannel + + echoTestString := "/echo test" + + if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, echoTestString)).(*model.CommandResponse); r1 == nil { + t.Fatal("Echo command failed to execute") + } + + if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/echo ")).(*model.CommandResponse); r1 == nil { + t.Fatal("Echo command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPostsForChannel(channel1.Id, 0, 2, "")).(*model.PostList) + if len(p1.Order) != 2 { + t.Fatal("Echo command failed to send") + } +} + +func TestGroupmsgCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user1 := th.BasicUser + user2 := th.BasicUser2 + user3 := th.CreateUser() + user4 := th.CreateUser() + user5 := th.CreateUser() + user6 := th.CreateUser() + user7 := th.CreateUser() + user8 := th.CreateUser() + user9 := th.CreateUser() + th.LinkUserToTeam(user3, team) + th.LinkUserToTeam(user4, team) + + rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse) + + group1 := model.GetGroupNameFromUserIds([]string{user1.Id, user2.Id, user3.Id}) + + if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+group1) { + t.Fatal("failed to create group channel") + } + + rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user3.Username+","+user4.Username+" foobar")).(*model.CommandResponse) + group2 := model.GetGroupNameFromUserIds([]string{user1.Id, user3.Id, user4.Id}) + + if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+group2) { + t.Fatal("failed to create second direct channel") + } + if result := Client.Must(Client.SearchPosts(team.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 { + t.Fatal("post did not get sent to direct message") + } + + rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse) + if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+group1) { + t.Fatal("failed to go back to existing group channel") + } + + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+" foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username+","+user4.Username+","+user5.Username+","+user6.Username+","+user7.Username+","+user8.Username+","+user9.Username+" foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg junk foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg junk,junk2 foobar")) +} + +func TestInvitePeopleCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test@example.com")).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + r2 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test1@example.com test2@example.com")).(*model.CommandResponse) + if r2 == nil { + t.Fatal("Command failed to execute") + } + + r3 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people")).(*model.CommandResponse) + if r3 == nil { + t.Fatal("Command failed to execute") + } +} + +// also used to test /open (see command_open_test.go) +func testJoinCommands(t *testing.T, alias string) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user2 := th.BasicUser2 + + channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel0 = Client.Must(Client.CreateChannel(channel0)).(*model.Channel) + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).(*model.Channel) + Client.Must(Client.RemoveUserFromChannel(channel1.Id, th.BasicUser.Id)) + + channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel2 = Client.Must(Client.CreateChannel(channel2)).(*model.Channel) + Client.Must(Client.RemoveUserFromChannel(channel2.Id, th.BasicUser.Id)) + + channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel) + + rs5 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel2.Name)).(*model.CommandResponse) + if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { + t.Fatal("failed to join channel") + } + + rs6 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel3.Name)).(*model.CommandResponse) + if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { + t.Fatal("should not have joined direct message channel") + } + + c1 := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel) + + found := false + for _, c := range c1 { + if c.Id == channel2.Id { + found = true + } + } + + if !found { + t.Fatal("did not join channel") + } +} + +func TestJoinCommands(t *testing.T) { + testJoinCommands(t, "join") +} + +func TestLoadTestHelpCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test help")).(*model.CommandResponse) + if !strings.Contains(rs.Text, "Mattermost testing commands to help") { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestSetupCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test setup fuzz 1 1 1")).(*model.CommandResponse) + if rs.Text != "Created environment" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestUsersCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test users fuzz 1 2")).(*model.CommandResponse) + if rs.Text != "Added users" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestChannelsCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test channels fuzz 1 2")).(*model.CommandResponse) + if rs.Text != "Added channels" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestPostsCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test posts fuzz 2 3 2")).(*model.CommandResponse) + if rs.Text != "Added posts" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLeaveCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user2 := th.BasicUser2 + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).(*model.Channel) + Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser.Id)) + + channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} + channel2 = Client.Must(Client.CreateChannel(channel2)).(*model.Channel) + Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) + Client.Must(Client.AddChannelMember(channel2.Id, user2.Id)) + + channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel) + + rs1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/leave")).(*model.CommandResponse) + if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { + t.Fatal("failed to leave open channel 1") + } + + rs2 := Client.Must(Client.ExecuteCommand(channel2.Id, "/leave")).(*model.CommandResponse) + if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { + t.Fatal("failed to leave private channel 1") + } + + _, err := Client.ExecuteCommand(channel3.Id, "/leave") + if err == nil { + t.Fatal("should fail leaving direct channel") + } + + cdata := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel) + + found := false + for _, c := range cdata { + if c.Id == channel1.Id || c.Id == channel2.Id { + found = true + } + } + + if found { + t.Fatal("did not leave right channels") + } + + for _, c := range cdata { + if c.Name == model.DEFAULT_CHANNEL { + if _, err := Client.RemoveUserFromChannel(c.Id, th.BasicUser.Id); err == nil { + t.Fatal("should have errored on leaving default channel") + } + break + } + } +} + +func TestLogoutTestCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/logout")) +} + +func TestMeCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + testString := "/me hello" + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList) + if len(p1.Order) != 2 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `*hello*` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug response") + } + } +} + +func TestMsgCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user1 := th.BasicUser + user2 := th.BasicUser2 + user3 := th.CreateUser() + th.LinkUserToTeam(user3, team) + + Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)) + Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user3.Id)) + + rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse) + if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { + t.Fatal("failed to create direct channel") + } + + rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user3.Username+" foobar")).(*model.CommandResponse) + if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) { + t.Fatal("failed to create second direct channel") + } + if result := Client.Must(Client.SearchPosts(th.BasicTeam.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 { + t.Fatalf("post did not get sent to direct message") + } + + rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse) + if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { + t.Fatal("failed to go back to existing direct channel") + } + + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+th.BasicUser.Username+" foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg junk foobar")) +} + +func TestOpenCommands(t *testing.T) { + testJoinCommands(t, "open") +} + +func TestSearchCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/search")) +} + +func TestSettingsCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/settings")) +} + +func TestShortcutsCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/shortcuts")) +} + +func TestShrugCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + testString := "/shrug" + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList) + if len(p1.Order) != 2 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug response") + } + } +} + +func TestStatusCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + commandAndTest(t, th, "away") + commandAndTest(t, th, "offline") + commandAndTest(t, th, "online") +} + +func commandAndTest(t *testing.T, th *TestHelper, status string) { + Client := th.Client + channel := th.BasicChannel + user := th.BasicUser + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/"+status)).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(1000 * time.Millisecond) + + rstatus := Client.Must(Client.GetUserStatus(user.Id, "")).(*model.Status) + + if rstatus.Status != status { + t.Fatal("Error setting status " + status) + } +} diff --git a/api4/oauth_test.go b/api4/oauth_test.go index 0862f13f5..8cf20ca5e 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -4,12 +4,19 @@ package api4 import ( + "encoding/base64" + "io" + "io/ioutil" "net/http" "net/url" "strconv" "testing" + "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" ) func TestCreateOAuthApp(t *testing.T) { @@ -726,3 +733,409 @@ func TestDeauthorizeOAuthApp(t *testing.T) { _, resp = Client.DeauthorizeOAuthApp(rapp.Id) CheckUnauthorizedStatus(t, resp) } + +func TestOAuthAccessToken(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + + enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + + oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} + oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) + data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}} + + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Log(resp.StatusCode) + t.Fatal("should have failed - oauth providing turned off") + } + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) + + authRequest := &model.AuthorizeRequest{ + ResponseType: model.AUTHCODE_RESPONSE_TYPE, + ClientId: oauthApp.Id, + RedirectUri: oauthApp.CallbackUrls[0], + Scope: "all", + State: "123", + } + + redirect, resp := Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ := url.Parse(redirect) + + Client.Logout() + + data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}} + + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad grant type") + } + + data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + data.Set("client_id", "") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - missing client id") + } + data.Set("client_id", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad client id") + } + + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", "") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - missing client secret") + } + + data.Set("client_secret", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad client secret") + } + + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("code", "") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - missing code") + } + + data.Set("code", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad code") + } + + data.Set("code", rurl.Query().Get("code")) + data.Set("redirect_uri", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - non-matching redirect uri") + } + + // reset data for successful request + data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("code", rurl.Query().Get("code")) + data.Set("redirect_uri", oauthApp.CallbackUrls[0]) + + token := "" + refreshToken := "" + if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(rsp.AccessToken) == 0 { + t.Fatal("access token not returned") + } else if len(rsp.RefreshToken) == 0 { + t.Fatal("refresh token not returned") + } else { + token = rsp.AccessToken + refreshToken = rsp.RefreshToken + } + if rsp.TokenType != model.ACCESS_TOKEN_TYPE { + t.Fatal("access token type incorrect") + } + } + + if _, err := Client.DoApiGet("/users?page=0&per_page=100&access_token="+token, ""); err != nil { + t.Fatal(err) + } + + if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil { + t.Fatal("should have failed - no access token provided") + } + + if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil { + t.Fatal("should have failed - bad access token provided") + } + + Client.SetOAuthToken(token) + if users, resp := Client.GetUsers(0, 100, ""); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(users) == 0 { + t.Fatal("users empty - did not get results correctly") + } + } + + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - tried to reuse auth code") + } + + data.Set("grant_type", model.REFRESH_TOKEN_GRANT_TYPE) + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("refresh_token", "") + data.Set("redirect_uri", oauthApp.CallbackUrls[0]) + data.Del("code") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("Should have failed - refresh token empty") + } + + data.Set("refresh_token", refreshToken) + if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(rsp.AccessToken) == 0 { + t.Fatal("access token not returned") + } else if len(rsp.RefreshToken) == 0 { + t.Fatal("refresh token not returned") + } else if rsp.RefreshToken == refreshToken { + t.Fatal("refresh token did not update") + } + + if rsp.TokenType != model.ACCESS_TOKEN_TYPE { + t.Fatal("access token type incorrect") + } + Client.SetOAuthToken(rsp.AccessToken) + _, resp = Client.GetMe("") + if resp.Error != nil { + t.Fatal(resp.Error) + } + + data.Set("refresh_token", rsp.RefreshToken) + } + + if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(rsp.AccessToken) == 0 { + t.Fatal("access token not returned") + } else if len(rsp.RefreshToken) == 0 { + t.Fatal("refresh token not returned") + } else if rsp.RefreshToken == refreshToken { + t.Fatal("refresh token did not update") + } + + if rsp.TokenType != model.ACCESS_TOKEN_TYPE { + t.Fatal("access token type incorrect") + } + Client.SetOAuthToken(rsp.AccessToken) + _, resp = Client.GetMe("") + if resp.Error != nil { + t.Fatal(resp.Error) + } + } + + authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1} + <-th.App.Srv.Store.OAuth().SaveAuthData(authData) + + data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("redirect_uri", oauthApp.CallbackUrls[0]) + data.Set("code", authData.Code) + data.Del("refresh_token") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("Should have failed - code is expired") + } + + Client.ClearOAuthToken() +} + +func TestOAuthComplete(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + + r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123", Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = true }) + r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = model.NewId() }) + + stateProps := map[string]string{} + stateProps["action"] = model.OAUTH_ACTION_LOGIN + stateProps["team_id"] = th.BasicTeam.Id + stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint + + state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id) + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + // We are going to use mattermost as the provider emulating gitlab + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + + oauthApp := &model.OAuthApp{ + Name: "TestApp5" + model.NewId(), + Homepage: "https://nowhere.com", + Description: "test", + CallbackUrls: []string{ + Client.Url + "/signup/" + model.SERVICE_GITLAB + "/complete", + Client.Url + "/login/" + model.SERVICE_GITLAB + "/complete", + }, + IsTrusted: true, + } + oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = oauthApp.Id }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = oauthApp.ClientSecret }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = Client.Url + "/oauth/access_token" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserApiEndpoint = Client.ApiUrl + "/users/me" }) + + provider := &MattermostTestProvider{} + + authRequest := &model.AuthorizeRequest{ + ResponseType: model.AUTHCODE_RESPONSE_TYPE, + ClientId: oauthApp.Id, + RedirectUri: oauthApp.CallbackUrls[0], + Scope: "all", + State: "123", + } + + redirect, resp := Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ := url.Parse(redirect) + + code := rurl.Query().Get("code") + stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO + delete(stateProps, "team_id") + stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint + stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id) + stateProps["redirect_to"] = "/oauth/authorize" + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + einterfaces.RegisterOauthProvider(model.SERVICE_GITLAB, provider) + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + if result := <-th.App.Srv.Store.User().UpdateAuthData( + th.BasicUser.Id, model.SERVICE_GITLAB, &th.BasicUser.Email, th.BasicUser.Email, true); result.Err != nil { + t.Fatal(result.Err) + } + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + stateProps["action"] = model.OAUTH_ACTION_LOGIN + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + delete(stateProps, "action") + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + stateProps["action"] = model.OAUTH_ACTION_SIGNUP + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } +} + +func HttpGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, *model.AppError) { + rq, _ := http.NewRequest("GET", url, nil) + rq.Close = true + + if len(authToken) > 0 { + rq.Header.Set(model.HEADER_AUTH, authToken) + } + + if !followRedirect { + httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + + if rp, err := httpClient.Do(rq); err != nil { + return nil, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) + } else if rp.StatusCode == 304 { + return rp, nil + } else if rp.StatusCode == 307 { + return rp, nil + } else if rp.StatusCode >= 300 { + defer closeBody(rp) + return rp, model.AppErrorFromJson(rp.Body) + } else { + return rp, nil + } +} + +func closeBody(r *http.Response) { + if r != nil && r.Body != nil { + ioutil.ReadAll(r.Body) + r.Body.Close() + } +} + +type MattermostTestProvider struct { +} + +func (m *MattermostTestProvider) GetIdentifier() string { + return model.SERVICE_GITLAB +} + +func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User { + return model.UserFromJson(data) +} + +func (m *MattermostTestProvider) GetAuthDataFromJson(data io.Reader) string { + authData := model.UserFromJson(data) + return authData.Email +} diff --git a/api4/websocket_test.go b/api4/websocket_test.go index fa40629dc..9e4c1da73 100644 --- a/api4/websocket_test.go +++ b/api4/websocket_test.go @@ -4,10 +4,16 @@ package api4 import ( + "fmt" + "net/http" + "strings" "testing" "time" + "github.com/gorilla/websocket" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" ) func TestWebSocket(t *testing.T) { @@ -71,3 +77,384 @@ func TestWebSocket(t *testing.T) { } } } + +func TestWebSocketEvent(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + WebSocketClient, err := th.CreateWebSocketClient() + if err != nil { + t.Fatal(err) + } + defer WebSocketClient.Close() + + WebSocketClient.Listen() + + time.Sleep(300 * time.Millisecond) + if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { + t.Fatal("should have responded OK to authentication challenge") + } + + omitUser := make(map[string]bool, 1) + omitUser["somerandomid"] = true + evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser) + evt1.Add("user_id", "somerandomid") + th.App.Publish(evt1) + + time.Sleep(300 * time.Millisecond) + + stop := make(chan bool) + eventHit := false + + go func() { + for { + select { + case resp := <-WebSocketClient.EventChannel: + if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" { + eventHit = true + } + case <-stop: + return + } + } + }() + + time.Sleep(400 * time.Millisecond) + + stop <- true + + if !eventHit { + t.Fatal("did not receive typing event") + } + + evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil) + th.App.Publish(evt2) + time.Sleep(300 * time.Millisecond) + + eventHit = false + + go func() { + for { + select { + case resp := <-WebSocketClient.EventChannel: + if resp.Event == model.WEBSOCKET_EVENT_TYPING { + eventHit = true + } + case <-stop: + return + } + } + }() + + time.Sleep(400 * time.Millisecond) + + stop <- true + + if eventHit { + t.Fatal("got typing event for bad channel id") + } +} + +func TestCreateDirectChannelWithSocket(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + user2 := th.BasicUser2 + + users := make([]*model.User, 0) + users = append(users, user2) + + for i := 0; i < 10; i++ { + users = append(users, th.CreateUser()) + } + + WebSocketClient, err := th.CreateWebSocketClient() + if err != nil { + t.Fatal(err) + } + defer WebSocketClient.Close() + WebSocketClient.Listen() + + time.Sleep(300 * time.Millisecond) + if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { + t.Fatal("should have responded OK to authentication challenge") + } + + wsr := <-WebSocketClient.EventChannel + if wsr.Event != model.WEBSOCKET_EVENT_HELLO { + t.Fatal("missing hello") + } + + stop := make(chan bool) + count := 0 + + go func() { + for { + select { + case wsr := <-WebSocketClient.EventChannel: + if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED { + count = count + 1 + } + + case <-stop: + return + } + } + }() + + for _, user := range users { + time.Sleep(100 * time.Millisecond) + if _, resp := Client.CreateDirectChannel(th.BasicUser.Id, user.Id); resp.Error != nil { + t.Fatal("failed to create DM channel") + } + } + + time.Sleep(5000 * time.Millisecond) + + stop <- true + + if count != len(users) { + t.Fatal("We didn't get the proper amount of direct_added messages") + } + +} + +func TestWebsocketOriginSecurity(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + url := fmt.Sprintf("ws://localhost:%v", th.App.Srv.ListenAddr.Port) + + // Should fail because origin doesn't match + _, _, err := websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err == nil { + t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") + } + + // We are not a browser so we can spoof this just fine + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port)}, + }) + if err != nil { + t.Fatal(err) + } + + // Should succeed now because open CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "*" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err != nil { + t.Fatal(err) + } + + // Should succeed now because matching CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.evil.com" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err != nil { + t.Fatal(err) + } + + // Should fail because non-matching CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err == nil { + t.Fatal("Should have errored because Origin contain AllowCorsFrom") + } + + // Should fail because non-matching CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.good.co"}, + }) + if err == nil { + t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") + } + + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "" }) +} + +func TestWebSocketStatuses(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + WebSocketClient, err := th.CreateWebSocketClient() + if err != nil { + t.Fatal(err) + } + defer WebSocketClient.Close() + WebSocketClient.Listen() + + time.Sleep(300 * time.Millisecond) + if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { + t.Fatal("should have responded OK to authentication challenge") + } + + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + rteam, _ := Client.CreateTeam(&team) + + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} + ruser := Client.Must(Client.CreateUser(&user)).(*model.User) + th.LinkUserToTeam(ruser, rteam) + store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) + + user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} + ruser2 := Client.Must(Client.CreateUser(&user2)).(*model.User) + th.LinkUserToTeam(ruser2, rteam) + store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Id)) + + Client.Login(user.Email, user.Password) + + th.LoginBasic2() + + WebSocketClient2, err2 := th.CreateWebSocketClient() + if err2 != nil { + t.Fatal(err2) + } + + time.Sleep(1000 * time.Millisecond) + + WebSocketClient.GetStatuses() + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + for _, status := range resp.Data { + if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE && status != model.STATUS_DND { + t.Fatalf("one of the statuses had an invalid value status=%v", status) + } + } + + if status, ok := resp.Data[th.BasicUser2.Id]; !ok { + t.Log(resp.Data) + t.Fatal("should have had user status") + } else if status != model.STATUS_ONLINE { + t.Log(status) + t.Fatal("status should have been online") + } + } + + WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id}) + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + for _, status := range resp.Data { + if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { + t.Fatal("one of the statuses had an invalid value") + } + } + + if status, ok := resp.Data[th.BasicUser2.Id]; !ok { + t.Log(len(resp.Data)) + t.Fatal("should have had user status") + } else if status != model.STATUS_ONLINE { + t.Log(status) + t.Fatal("status should have been online") + } else if len(resp.Data) != 1 { + t.Fatal("only 1 status should be returned") + } + } + + WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"}) + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + if len(resp.Data) != 2 { + t.Fatal("2 statuses should be returned") + } + } + + WebSocketClient.GetStatusesByIds([]string{}) + if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + t.Fatal("should have errored - empty user ids") + } + + WebSocketClient2.Close() + + th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) + + awayTimeout := *th.App.Config().TeamSettings.UserStatusAwayTimeout + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = awayTimeout }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = 1 }) + + time.Sleep(1500 * time.Millisecond) + + th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) + th.App.SetStatusOnline(th.BasicUser.Id, "junk", false) + + time.Sleep(1500 * time.Millisecond) + + WebSocketClient.GetStatuses() + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + if _, ok := resp.Data[th.BasicUser2.Id]; ok { + t.Fatal("should not have had user status") + } + } + + stop := make(chan bool) + onlineHit := false + awayHit := false + + go func() { + for { + select { + case resp := <-WebSocketClient.EventChannel: + if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { + status := resp.Data["status"].(string) + if status == model.STATUS_ONLINE { + onlineHit = true + } else if status == model.STATUS_AWAY { + awayHit = true + } + } + case <-stop: + return + } + } + }() + + time.Sleep(500 * time.Millisecond) + + stop <- true + + if !onlineHit { + t.Fatal("didn't get online event") + } + if !awayHit { + t.Fatal("didn't get away event") + } + + time.Sleep(500 * time.Millisecond) + + WebSocketClient.Close() +} diff --git a/app/auto_channels.go b/app/auto_channels.go index 78b500961..72561d2b9 100644 --- a/app/auto_channels.go +++ b/app/auto_channels.go @@ -9,7 +9,7 @@ import ( ) type AutoChannelCreator struct { - client *model.Client + client *model.Client4 team *model.Team Fuzzy bool DisplayNameLen utils.Range @@ -19,7 +19,7 @@ type AutoChannelCreator struct { ChannelType string } -func NewAutoChannelCreator(client *model.Client, team *model.Team) *AutoChannelCreator { +func NewAutoChannelCreator(client *model.Client4, team *model.Team) *AutoChannelCreator { return &AutoChannelCreator{ client: client, team: team, @@ -47,14 +47,14 @@ func (cfg *AutoChannelCreator) createRandomChannel() (*model.Channel, bool) { Name: name, Type: cfg.ChannelType} - println(cfg.client.GetTeamRoute()) - result, err := cfg.client.CreateChannel(channel) - if err != nil { - println(err.Error()) - println(err.DetailedError) + println(cfg.client.GetTeamRoute(cfg.team.Id)) + channel, resp := cfg.client.CreateChannel(channel) + if resp.Error != nil { + println(resp.Error.Error()) + println(resp.Error.DetailedError) return nil, false } - return result.Data.(*model.Channel), true + return channel, true } func (cfg *AutoChannelCreator) CreateTestChannels(num utils.Range) ([]*model.Channel, bool) { diff --git a/app/auto_environment.go b/app/auto_environment.go index 476a1c211..688940337 100644 --- a/app/auto_environment.go +++ b/app/auto_environment.go @@ -16,7 +16,7 @@ type TestEnvironment struct { Environments []TeamEnvironment } -func CreateTestEnvironmentWithTeams(a *App, client *model.Client, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, bool) { +func CreateTestEnvironmentWithTeams(a *App, client *model.Client4, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, bool) { rand.Seed(time.Now().UTC().UnixNano()) teamCreator := NewAutoTeamCreator(client) @@ -36,7 +36,6 @@ func CreateTestEnvironmentWithTeams(a *App, client *model.Client, rangeTeams uti return TestEnvironment{}, false } client.LoginById(randomUser.Id, USER_PASSWORD) - client.SetTeamId(team.Id) teamEnvironment, err := CreateTestEnvironmentInTeam(a, client, team, rangeChannels, rangeUsers, rangePosts, fuzzy) if !err { return TestEnvironment{}, false @@ -47,7 +46,7 @@ func CreateTestEnvironmentWithTeams(a *App, client *model.Client, rangeTeams uti return environment, true } -func CreateTestEnvironmentInTeam(a *App, client *model.Client, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) { +func CreateTestEnvironmentInTeam(a *App, client *model.Client4, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) { rand.Seed(time.Now().UTC().UnixNano()) // We need to create at least one user @@ -74,7 +73,7 @@ func CreateTestEnvironmentInTeam(a *App, client *model.Client, team *model.Team, for _, user := range users { for _, channel := range channels { client.LoginById(user.Id, USER_PASSWORD) - client.JoinChannel(channel.Id) + client.AddChannelMember(channel.Id, user.Id) } } diff --git a/app/auto_posts.go b/app/auto_posts.go index a2adb9d6c..23746a9ba 100644 --- a/app/auto_posts.go +++ b/app/auto_posts.go @@ -14,7 +14,7 @@ import ( ) type AutoPostCreator struct { - client *model.Client + client *model.Client4 channelid string Fuzzy bool TextLength utils.Range @@ -26,7 +26,7 @@ type AutoPostCreator struct { } // Automatic poster used for testing -func NewAutoPostCreator(client *model.Client, channelid string) *AutoPostCreator { +func NewAutoPostCreator(client *model.Client4, channelid string) *AutoPostCreator { return &AutoPostCreator{ client: client, channelid: channelid, @@ -56,7 +56,7 @@ func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) { return nil, false } - resp, appErr := cfg.client.UploadPostAttachment(data.Bytes(), cfg.channelid, filename) + resp, appErr := cfg.client.UploadFile(data.Bytes(), cfg.channelid, filename) if appErr != nil { return nil, false } @@ -85,9 +85,9 @@ func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) { ChannelId: cfg.channelid, Message: postText, FileIds: fileIds} - result, err2 := cfg.client.CreatePost(post) + rpost, err2 := cfg.client.CreatePost(post) if err2 != nil { return nil, false } - return result.Data.(*model.Post), true + return rpost, true } diff --git a/app/auto_teams.go b/app/auto_teams.go index 9dce5bd15..e322e8c66 100644 --- a/app/auto_teams.go +++ b/app/auto_teams.go @@ -14,7 +14,7 @@ type TeamEnvironment struct { } type AutoTeamCreator struct { - client *model.Client + client *model.Client4 Fuzzy bool NameLength utils.Range NameCharset string @@ -24,7 +24,7 @@ type AutoTeamCreator struct { EmailCharset string } -func NewAutoTeamCreator(client *model.Client) *AutoTeamCreator { +func NewAutoTeamCreator(client *model.Client4) *AutoTeamCreator { return &AutoTeamCreator{ client: client, Fuzzy: false, @@ -57,11 +57,10 @@ func (cfg *AutoTeamCreator) createRandomTeam() (*model.Team, bool) { Type: model.TEAM_OPEN, } - result, err := cfg.client.CreateTeam(team) + createdTeam, err := cfg.client.CreateTeam(team) if err != nil { return nil, false } - createdTeam := result.Data.(*model.Team) return createdTeam, true } diff --git a/app/auto_users.go b/app/auto_users.go index b11f9c572..b61ab053f 100644 --- a/app/auto_users.go +++ b/app/auto_users.go @@ -12,7 +12,7 @@ import ( type AutoUserCreator struct { app *App - client *model.Client + client *model.Client4 team *model.Team EmailLength utils.Range EmailCharset string @@ -21,7 +21,7 @@ type AutoUserCreator struct { Fuzzy bool } -func NewAutoUserCreator(a *App, client *model.Client, team *model.Team) *AutoUserCreator { +func NewAutoUserCreator(a *App, client *model.Client4, team *model.Team) *AutoUserCreator { return &AutoUserCreator{ app: a, client: client, @@ -35,21 +35,19 @@ func NewAutoUserCreator(a *App, client *model.Client, team *model.Team) *AutoUse } // Basic test team and user so you always know one -func (a *App) CreateBasicUser(client *model.Client) *model.AppError { - result, _ := client.FindTeamByName(BTEST_TEAM_NAME) - if !result.Data.(bool) { +func (a *App) CreateBasicUser(client *model.Client4) *model.AppError { + found, _ := client.TeamExists(BTEST_TEAM_NAME, "") + if !found { newteam := &model.Team{DisplayName: BTEST_TEAM_DISPLAY_NAME, Name: BTEST_TEAM_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE} - result, err := client.CreateTeam(newteam) - if err != nil { - return err + basicteam, resp := client.CreateTeam(newteam) + if resp.Error != nil { + return resp.Error } - basicteam := result.Data.(*model.Team) newuser := &model.User{Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD} - result, err = client.CreateUser(newuser, "") - if err != nil { - return err + ruser, resp := client.CreateUser(newuser) + if resp.Error != nil { + return resp.Error } - ruser := result.Data.(*model.User) store.Must(a.Srv.Store.User().VerifyEmail(ruser.Id)) store.Must(a.Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id}, *a.Config().TeamSettings.MaxUsersPerTeam)) } @@ -72,14 +70,12 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { Nickname: userName, Password: USER_PASSWORD} - result, err := cfg.client.CreateUserWithInvite(user, "", "", cfg.team.InviteId) - if err != nil { - mlog.Error(err.Error()) + ruser, resp := cfg.client.CreateUserWithInviteId(user, cfg.team.InviteId) + if resp.Error != nil { + mlog.Error(resp.Error.Error()) return nil, false } - ruser := result.Data.(*model.User) - status := &model.Status{UserId: ruser.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} if result := <-cfg.app.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { mlog.Error(result.Err.Error()) @@ -89,7 +85,7 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { // We need to cheat to verify the user's email store.Must(cfg.app.Srv.Store.User().VerifyEmail(ruser.Id)) - return result.Data.(*model.User), true + return ruser, true } func (cfg *AutoUserCreator) CreateTestUsers(num utils.Range) ([]*model.User, bool) { diff --git a/app/command_loadtest.go b/app/command_loadtest.go index a4a8f003a..8b3cd2e3b 100644 --- a/app/command_loadtest.go +++ b/app/command_loadtest.go @@ -159,7 +159,7 @@ func (me *LoadTestProvider) SetupCommand(a *App, args *model.CommandArgs, messag numPosts, _ = strconv.Atoi(tokens[numArgs+2]) } } - client := model.NewClient(args.SiteURL) + client := model.NewAPIv4Client(args.SiteURL) if doTeams { if err := a.CreateBasicUser(client); err != nil { @@ -193,7 +193,6 @@ func (me *LoadTestProvider) SetupCommand(a *App, args *model.CommandArgs, messag } client.MockSession(args.Session.Token) - client.SetTeamId(args.TeamId) CreateTestEnvironmentInTeam( a, client, @@ -228,8 +227,7 @@ func (me *LoadTestProvider) UsersCommand(a *App, args *model.CommandArgs, messag team = tr.Data.(*model.Team) } - client := model.NewClient(args.SiteURL) - client.SetTeamId(team.Id) + client := model.NewAPIv4Client(args.SiteURL) userCreator := NewAutoUserCreator(a, client, team) userCreator.Fuzzy = doFuzz userCreator.CreateTestUsers(usersr) @@ -258,8 +256,7 @@ func (me *LoadTestProvider) ChannelsCommand(a *App, args *model.CommandArgs, mes team = tr.Data.(*model.Team) } - client := model.NewClient(args.SiteURL) - client.SetTeamId(team.Id) + client := model.NewAPIv4Client(args.SiteURL) client.MockSession(args.Session.Token) channelCreator := NewAutoChannelCreator(client, team) channelCreator.Fuzzy = doFuzz @@ -301,8 +298,7 @@ func (me *LoadTestProvider) PostsCommand(a *App, args *model.CommandArgs, messag } } - client := model.NewClient(args.SiteURL) - client.SetTeamId(args.TeamId) + client := model.NewAPIv4Client(args.SiteURL) client.MockSession(args.Session.Token) testPoster := NewAutoPostCreator(client, args.ChannelId) testPoster.Fuzzy = doFuzz diff --git a/app/diagnostics.go b/app/diagnostics.go index 7dcea839e..fc8a2e886 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -6,7 +6,6 @@ package app import ( "encoding/json" "runtime" - "sync/atomic" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" @@ -181,10 +180,7 @@ func (a *App) trackActivity() { "public_channels_deleted": deletedPublicChannelCount, "private_channels_deleted": deletedPrivateChannelCount, "posts": postsCount, - "used_apiv3": atomic.LoadInt32(model.UsedApiV3) == 1, }) - - atomic.StoreInt32(model.UsedApiV3, 0) } func (a *App) trackConfig() { @@ -199,7 +195,6 @@ func (a *App) trackConfig() { "enable_only_admin_integrations": *cfg.ServiceSettings.EnableOnlyAdminIntegrations, "enable_post_username_override": cfg.ServiceSettings.EnablePostUsernameOverride, "enable_post_icon_override": cfg.ServiceSettings.EnablePostIconOverride, - "enable_apiv3": *cfg.ServiceSettings.EnableAPIv3, "enable_user_access_tokens": *cfg.ServiceSettings.EnableUserAccessTokens, "enable_custom_emoji": *cfg.ServiceSettings.EnableCustomEmoji, "enable_emoji_picker": *cfg.ServiceSettings.EnableEmojiPicker, diff --git a/app/file.go b/app/file.go index cb8d54cb1..aba09479a 100644 --- a/app/file.go +++ b/app/file.go @@ -295,11 +295,6 @@ func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string { return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash) } -func (a *App) GeneratePublicLinkV3(siteURL string, info *model.FileInfo) string { - hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt) - return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX_V3, info.Id, hash) -} - func GeneratePublicLinkHash(fileId, salt string) string { hash := sha256.New() hash.Write([]byte(salt)) diff --git a/app/file_test.go b/app/file_test.go index 204113782..23750ad6e 100644 --- a/app/file_test.go +++ b/app/file_test.go @@ -5,10 +5,16 @@ package app import ( "fmt" + "os" + "path/filepath" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" ) func TestGeneratePublicLinkHash(t *testing.T) { @@ -100,3 +106,58 @@ func TestDoUploadFile(t *testing.T) { t.Fatal("stored file at incorrect path", info4.Path) } } + +func TestGetInfoForFilename(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + post := th.BasicPost + teamId := th.BasicTeam.Id + + info := th.App.GetInfoForFilename(post, teamId, "sometestfile") + assert.Nil(t, info, "Test bad filename") + + info = th.App.GetInfoForFilename(post, teamId, "/somechannel/someuser/someid/somefile.png") + assert.Nil(t, info, "Test non-existent file") +} + +func TestFindTeamIdForFilename(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + teamId := th.App.FindTeamIdForFilename(th.BasicPost, fmt.Sprintf("/%v/%v/%v/blargh.png", th.BasicChannel.Id, th.BasicUser.Id, "someid")) + assert.Equal(t, th.BasicTeam.Id, teamId) + + _, err := th.App.CreateTeamWithUser(&model.Team{Email: th.BasicUser.Email, Name: "zz" + model.NewId(), DisplayName: "Joram's Test Team", Type: model.TEAM_OPEN}, th.BasicUser.Id) + require.Nil(t, err) + + teamId = th.App.FindTeamIdForFilename(th.BasicPost, fmt.Sprintf("/%v/%v/%v/blargh.png", th.BasicChannel.Id, th.BasicUser.Id, "someid")) + assert.Equal(t, "", teamId) +} + +func TestMigrateFilenamesToFileInfos(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + post := th.BasicPost + infos := th.App.MigrateFilenamesToFileInfos(post) + assert.Equal(t, 0, len(infos)) + + post.Filenames = []string{fmt.Sprintf("/%v/%v/%v/blargh.png", th.BasicChannel.Id, th.BasicUser.Id, "someid")} + infos = th.App.MigrateFilenamesToFileInfos(post) + assert.Equal(t, 0, len(infos)) + + path, _ := utils.FindDir("tests") + file, fileErr := os.Open(filepath.Join(path, "test.png")) + require.Nil(t, fileErr) + defer file.Close() + + fpath := fmt.Sprintf("/teams/%v/channels/%v/users/%v/%v/test.png", th.BasicTeam.Id, th.BasicChannel.Id, th.BasicUser.Id, "someid") + _, err := th.App.WriteFile(file, fpath) + require.Nil(t, err) + rpost, err := th.App.CreatePost(&model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/test.png", th.BasicChannel.Id, th.BasicUser.Id, "someid")}}, th.BasicChannel, false) + require.Nil(t, err) + + infos = th.App.MigrateFilenamesToFileInfos(rpost) + assert.Equal(t, 1, len(infos)) +} diff --git a/cmd/commands/channel_test.go b/cmd/commands/channel_test.go index bd19b020a..09747b10b 100644 --- a/cmd/commands/channel_test.go +++ b/cmd/commands/channel_test.go @@ -7,17 +7,17 @@ import ( "strings" "testing" - "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" "github.com/stretchr/testify/require" ) func TestJoinChannel(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() - channel := th.CreateChannel(th.BasicClient, th.BasicTeam) + channel := th.CreatePublicChannel() cmd.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) @@ -29,10 +29,10 @@ func TestJoinChannel(t *testing.T) { } func TestRemoveChannel(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() - channel := th.CreateChannel(th.BasicClient, th.BasicTeam) + channel := th.CreatePublicChannel() cmd.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) @@ -46,12 +46,11 @@ func TestRemoveChannel(t *testing.T) { } func TestMoveChannel(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() - client := th.BasicClient team1 := th.BasicTeam - team2 := th.CreateTeam(client) + team2 := th.CreateTeam() user1 := th.BasicUser th.LinkUserToTeam(user1, team2) channel := th.BasicChannel @@ -73,11 +72,11 @@ func TestMoveChannel(t *testing.T) { } func TestListChannels(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() - channel := th.CreateChannel(th.BasicClient, th.BasicTeam) - th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id)) + channel := th.CreatePublicChannel() + th.Client.Must(th.Client.DeleteChannel(channel.Id)) output := cmd.CheckCommand(t, "channel", "list", th.BasicTeam.Name) @@ -91,11 +90,11 @@ func TestListChannels(t *testing.T) { } func TestRestoreChannel(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() - channel := th.CreateChannel(th.BasicClient, th.BasicTeam) - th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id)) + channel := th.CreatePublicChannel() + th.Client.Must(th.Client.DeleteChannel(channel.Id)) cmd.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) @@ -104,7 +103,7 @@ func TestRestoreChannel(t *testing.T) { } func TestCreateChannel(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() id := model.NewId() diff --git a/cmd/commands/roles_test.go b/cmd/commands/roles_test.go index 1e0a46a4e..b6b3ae10f 100644 --- a/cmd/commands/roles_test.go +++ b/cmd/commands/roles_test.go @@ -6,13 +6,13 @@ package commands import ( "testing" - "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" ) func TestAssignRole(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() cmd.CheckCommand(t, "roles", "system_admin", th.BasicUser.Email) diff --git a/cmd/commands/sampledata_test.go b/cmd/commands/sampledata_test.go index d71ac0575..183392f11 100644 --- a/cmd/commands/sampledata_test.go +++ b/cmd/commands/sampledata_test.go @@ -6,13 +6,13 @@ package commands import ( "testing" - "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/cmd" "github.com/stretchr/testify/require" ) func TestSampledataBadParameters(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() // should fail because you need at least 1 worker diff --git a/cmd/commands/server.go b/cmd/commands/server.go index 441eb82bc..0d354f08e 100644 --- a/cmd/commands/server.go +++ b/cmd/commands/server.go @@ -11,7 +11,6 @@ import ( "syscall" "time" - "github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/cmd" @@ -104,8 +103,7 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan return serverErr } - api4.Init(a, a.Srv.Router, false) - api3 := api.Init(a, a.Srv.Router) + api := api4.Init(a, a.Srv.Router) wsapi.Init(a, a.Srv.WebSocketRouter) web.NewWeb(a, a.Srv.Router) @@ -135,7 +133,7 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan // If we allow testing then listen for manual testing URL hits if a.Config().ServiceSettings.EnableTesting { - manualtesting.Init(api3) + manualtesting.Init(api) } a.EnsureDiagnosticId() diff --git a/cmd/commands/team_test.go b/cmd/commands/team_test.go index ac006dbe1..e6bc47a09 100644 --- a/cmd/commands/team_test.go +++ b/cmd/commands/team_test.go @@ -7,13 +7,13 @@ import ( "strings" "testing" - "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" ) func TestCreateTeam(t *testing.T) { - th := api.Setup().InitSystemAdmin() + th := api4.Setup().InitSystemAdmin() defer th.TearDown() id := model.NewId() @@ -22,7 +22,7 @@ func TestCreateTeam(t *testing.T) { cmd.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - found := th.SystemAdminClient.Must(th.SystemAdminClient.FindTeamByName(name)).Data.(bool) + found := th.SystemAdminClient.Must(th.SystemAdminClient.TeamExists(name, "")).(bool) if !found { t.Fatal("Failed to create Team") @@ -30,12 +30,12 @@ func TestCreateTeam(t *testing.T) { } func TestJoinTeam(t *testing.T) { - th := api.Setup().InitSystemAdmin().InitBasic() + th := api4.Setup().InitSystemAdmin().InitBasic() defer th.TearDown() - cmd.CheckCommand(t, "team", "add", th.SystemAdminTeam.Name, th.BasicUser.Email) + cmd.CheckCommand(t, "team", "add", th.BasicTeam.Name, th.BasicUser.Email) - profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User) + profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User) found := false @@ -52,12 +52,12 @@ func TestJoinTeam(t *testing.T) { } func TestLeaveTeam(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() cmd.CheckCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email) - profiles := th.BasicClient.Must(th.BasicClient.GetProfilesInTeam(th.BasicTeam.Id, 0, 1000, "")).Data.(map[string]*model.User) + profiles := th.Client.Must(th.Client.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User) found := false @@ -81,7 +81,7 @@ func TestLeaveTeam(t *testing.T) { } func TestListTeams(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() id := model.NewId() diff --git a/cmd/commands/test.go b/cmd/commands/test.go index 62df16438..837b988cc 100644 --- a/cmd/commands/test.go +++ b/cmd/commands/test.go @@ -12,7 +12,6 @@ import ( "os/signal" "syscall" - "github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" @@ -60,8 +59,7 @@ func webClientTestsCmdF(command *cobra.Command, args []string) error { return serverErr } - api4.Init(a, a.Srv.Router, false) - api.Init(a, a.Srv.Router) + api4.Init(a, a.Srv.Router) wsapi.Init(a, a.Srv.WebSocketRouter) a.UpdateConfig(setupClientTests) runWebClientTests() @@ -82,8 +80,7 @@ func serverForWebClientTestsCmdF(command *cobra.Command, args []string) error { return serverErr } - api4.Init(a, a.Srv.Router, false) - api.Init(a, a.Srv.Router) + api4.Init(a, a.Srv.Router) wsapi.Init(a, a.Srv.WebSocketRouter) a.UpdateConfig(setupClientTests) diff --git a/cmd/commands/user_test.go b/cmd/commands/user_test.go index 8691ac803..e51a6150b 100644 --- a/cmd/commands/user_test.go +++ b/cmd/commands/user_test.go @@ -6,14 +6,14 @@ package commands import ( "testing" - "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/cmd" "github.com/mattermost/mattermost-server/model" "github.com/stretchr/testify/require" ) func TestCreateUserWithTeam(t *testing.T) { - th := api.Setup().InitSystemAdmin() + th := api4.Setup().InitBasic().InitSystemAdmin() defer th.TearDown() id := model.NewId() @@ -22,9 +22,9 @@ func TestCreateUserWithTeam(t *testing.T) { cmd.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) - cmd.CheckCommand(t, "team", "add", th.SystemAdminTeam.Id, email) + cmd.CheckCommand(t, "team", "add", th.BasicTeam.Id, email) - profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User) + profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User) found := false @@ -41,7 +41,7 @@ func TestCreateUserWithTeam(t *testing.T) { } func TestCreateUserWithoutTeam(t *testing.T) { - th := api.Setup() + th := api4.Setup() defer th.TearDown() id := model.NewId() @@ -61,18 +61,18 @@ func TestCreateUserWithoutTeam(t *testing.T) { } func TestResetPassword(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() cmd.CheckCommand(t, "user", "password", th.BasicUser.Email, "password2") - th.BasicClient.Logout() + th.Client.Logout() th.BasicUser.Password = "password2" th.LoginBasic() } func TestMakeUserActiveAndInactive(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() // first inactivate the user @@ -83,7 +83,7 @@ func TestMakeUserActiveAndInactive(t *testing.T) { } func TestChangeUserEmail(t *testing.T) { - th := api.Setup().InitBasic() + th := api4.Setup().InitBasic() defer th.TearDown() newEmail := model.NewId() + "@mattermost-test.com" diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index 7b78fd312..2dbe61343 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -12,28 +12,29 @@ import ( "strconv" "time" - "github.com/mattermost/mattermost-server/api" + "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" + "github.com/mattermost/mattermost-server/web" ) type TestEnvironment struct { Params map[string][]string - Client *model.Client + Client *model.Client4 CreatedTeamId string CreatedUserId string - Context *api.Context + Context *web.Context Writer http.ResponseWriter Request *http.Request } -func Init(api3 *api.API) { - api3.BaseRoutes.Root.Handle("/manualtest", api3.AppHandler(manualTest)).Methods("GET") +func Init(api4 *api4.API) { + api4.BaseRoutes.Root.Handle("/manualtest", api4.ApiHandler(manualTest)).Methods("GET") } -func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { +func manualTest(c *web.Context, w http.ResponseWriter, r *http.Request) { // Let the world know mlog.Info("Setting up for manual test...") @@ -56,7 +57,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { } // Create a client for tests to use - client := model.NewClient("http://localhost" + *c.App.Config().ServiceSettings.ListenAddress) + client := model.NewAPIv4Client("http://localhost" + *c.App.Config().ServiceSettings.ListenAddress) // Check for username parameter and create a user if present username, ok1 := params["username"] @@ -95,22 +96,21 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { Nickname: username[0], Password: app.USER_PASSWORD} - result, err := client.CreateUser(user, "") - if err != nil { - c.Err = err + user, resp := client.CreateUser(user) + if resp.Error != nil { + c.Err = resp.Error return } - <-c.App.Srv.Store.User().VerifyEmail(result.Data.(*model.User).Id) - <-c.App.Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: teamID, UserId: result.Data.(*model.User).Id}, *c.App.Config().TeamSettings.MaxUsersPerTeam) + <-c.App.Srv.Store.User().VerifyEmail(user.Id) + <-c.App.Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: teamID, UserId: user.Id}, *c.App.Config().TeamSettings.MaxUsersPerTeam) - newuser := result.Data.(*model.User) - userID = newuser.Id + userID = user.Id // Login as user to generate auth token - _, err = client.LoginById(newuser.Id, app.USER_PASSWORD) - if err != nil { - c.Err = err + _, resp = client.LoginById(user.Id, app.USER_PASSWORD) + if resp.Error != nil { + c.Err = resp.Error return } diff --git a/manualtesting/test_autolink.go b/manualtesting/test_autolink.go index 3fe589241..05fd45551 100644 --- a/manualtesting/test_autolink.go +++ b/manualtesting/test_autolink.go @@ -31,6 +31,6 @@ func testAutoLink(env TestEnvironment) *model.AppError { post := &model.Post{ ChannelId: channelID, Message: LINK_POST_TEXT} - _, err2 := env.Client.CreatePost(post) - return err2 + _, resp := env.Client.CreatePost(post) + return resp.Error } diff --git a/model/client.go b/model/client.go deleted file mode 100644 index e648ca279..000000000 --- a/model/client.go +++ /dev/null @@ -1,2379 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/url" - "strconv" - "strings" - "time" -) - -var UsedApiV3 *int32 = new(int32) - -const ( - HEADER_REQUEST_ID = "X-Request-ID" - HEADER_VERSION_ID = "X-Version-ID" - HEADER_CLUSTER_ID = "X-Cluster-ID" - HEADER_ETAG_SERVER = "ETag" - HEADER_ETAG_CLIENT = "If-None-Match" - HEADER_FORWARDED = "X-Forwarded-For" - HEADER_REAL_IP = "X-Real-IP" - HEADER_FORWARDED_PROTO = "X-Forwarded-Proto" - HEADER_TOKEN = "token" - HEADER_BEARER = "BEARER" - HEADER_AUTH = "Authorization" - HEADER_REQUESTED_WITH = "X-Requested-With" - HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" - STATUS = "status" - STATUS_OK = "OK" - STATUS_FAIL = "FAIL" - STATUS_REMOVE = "REMOVE" - - CLIENT_DIR = "client" - - API_URL_SUFFIX_V1 = "/api/v1" - API_URL_SUFFIX_V3 = "/api/v3" - API_URL_SUFFIX_V4 = "/api/v4" - API_URL_SUFFIX = API_URL_SUFFIX_V4 -) - -type Result struct { - RequestId string - Etag string - Data interface{} -} - -type ResponseMetadata struct { - StatusCode int - Error *AppError - RequestId string - Etag string -} - -type Client struct { - Url string // The location of the server like "http://localhost:8065" - ApiUrl string // The api location of the server like "http://localhost:8065/api/v3" - HttpClient *http.Client // The http client - AuthToken string - AuthType string - TeamId string - RequestId string - Etag string - ServerVersion string -} - -// NewClient constructs a new client with convenience methods for talking to -// the server. -func NewClient(url string) *Client { - return &Client{url, url + API_URL_SUFFIX_V3, &http.Client{}, "", "", "", "", "", ""} -} - -func closeBody(r *http.Response) { - if r.Body != nil { - ioutil.ReadAll(r.Body) - r.Body.Close() - } -} - -func (c *Client) SetOAuthToken(token string) { - c.AuthToken = token - c.AuthType = HEADER_TOKEN -} - -func (c *Client) ClearOAuthToken() { - c.AuthToken = "" - c.AuthType = HEADER_BEARER -} - -func (c *Client) SetTeamId(teamId string) { - c.TeamId = teamId -} - -func (c *Client) GetTeamId() string { - if len(c.TeamId) == 0 { - println(`You are trying to use a route that requires a team_id, - but you have not called SetTeamId() in client.go`) - } - - return c.TeamId -} - -func (c *Client) ClearTeamId() { - c.TeamId = "" -} - -func (c *Client) GetTeamRoute() string { - return fmt.Sprintf("/teams/%v", c.GetTeamId()) -} - -func (c *Client) GetChannelRoute(channelId string) string { - return fmt.Sprintf("/teams/%v/channels/%v", c.GetTeamId(), channelId) -} - -func (c *Client) GetUserRequiredRoute(userId string) string { - return fmt.Sprintf("/users/%v", userId) -} - -func (c *Client) GetChannelNameRoute(channelName string) string { - return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) -} - -func (c *Client) GetEmojiRoute() string { - return "/emoji" -} - -func (c *Client) GetGeneralRoute() string { - return "/general" -} - -func (c *Client) GetFileRoute(fileId string) string { - return fmt.Sprintf("/files/%v", fileId) -} - -func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) { - rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return nil, AppErrorFromJson(rp.Body) - } else { - return rp, nil - } -} - -func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) { - rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data)) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return nil, AppErrorFromJson(rp.Body) - } else { - return rp, nil - } -} - -func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) { - rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data)) - rq.Close = true - - if len(etag) > 0 { - rq.Header.Set(HEADER_ETAG_CLIENT, etag) - } - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode == 304 { - return rp, nil - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return rp, AppErrorFromJson(rp.Body) - } else { - return rp, nil - } -} - -func getCookie(name string, resp *http.Response) *http.Cookie { - for _, cookie := range resp.Cookies() { - if cookie.Name == name { - return cookie - } - } - - return nil -} - -// Must is a convenience function used for testing. -func (c *Client) Must(result *Result, err *AppError) *Result { - if err != nil { - - time.Sleep(time.Second) - panic(err) - } - - return result -} - -// MustGeneric is a convenience function used for testing. -func (c *Client) MustGeneric(result interface{}, err *AppError) interface{} { - if err != nil { - - time.Sleep(time.Second) - panic(err) - } - - return result -} - -// CheckStatusOK is a convenience function for checking the return of Web Service -// call that return the a map of status=OK. -func (c *Client) CheckStatusOK(r *http.Response) bool { - m := MapFromJson(r.Body) - defer closeBody(r) - - if m != nil && m[STATUS] == STATUS_OK { - return true - } - - return false -} - -func (c *Client) fillInExtraProperties(r *http.Response) { - c.RequestId = r.Header.Get(HEADER_REQUEST_ID) - c.Etag = r.Header.Get(HEADER_ETAG_SERVER) - c.ServerVersion = r.Header.Get(HEADER_VERSION_ID) -} - -func (c *Client) clearExtraProperties() { - c.RequestId = "" - c.Etag = "" - c.ServerVersion = "" -} - -// General Routes Section - -// GetClientProperties returns properties needed by the client to show/hide -// certain features. It returns a map of strings. -func (c *Client) GetClientProperties() (map[string]string, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet(c.GetGeneralRoute()+"/client_props", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return MapFromJson(r.Body), nil - } -} - -// LogClient is a convenience Web Service call so clients can log messages into -// the server-side logs. For example we typically log javascript error messages -// into the server-side. It returns true if the logging was successful. -func (c *Client) LogClient(message string) (bool, *AppError) { - c.clearExtraProperties() - m := make(map[string]string) - m["level"] = "ERROR" - m["message"] = message - - if r, err := c.DoApiPost(c.GetGeneralRoute()+"/log_client", MapToJson(m)); err != nil { - return false, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -// GetPing returns a map of strings with server time, server version, and node Id. -// Systems that want to check on health status of the server should check the -// url /api/v3/ping for a 200 status response. -func (c *Client) GetPing() (map[string]string, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet(c.GetGeneralRoute()+"/ping", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return MapFromJson(r.Body), nil - } -} - -// Team Routes Section - -// CreateTeam creates a team based on the provided Team struct. On success it returns -// the Team struct with the Id, CreateAt and other server-decided fields populated. -func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { - if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// GetAllTeams returns a map of all teams using team ids as the key. -func (c *Client) GetAllTeams() (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil - } -} - -// GetAllTeamListings returns a map of all teams that are available to join -// using team ids as the key. Must be authenticated. -func (c *Client) GetAllTeamListings() (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil - } -} - -// FindTeamByName returns the strings "true" or "false" depending on if a team -// with the provided name was found. -func (c *Client) FindTeamByName(name string) (*Result, *AppError) { - m := make(map[string]string) - m["name"] = name - if r, err := c.DoApiPost("/teams/find_team_by_name", MapToJson(m)); err != nil { - return nil, err - } else { - val := false - if body, _ := ioutil.ReadAll(r.Body); string(body) == "true" { - val = true - } - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), val}, nil - } -} - -// Adds a user directly to the team without sending an invite. -// The teamId and userId are required. You must be a valid member of the team and/or -// have the correct role to add new users to the team. Returns a map of user_id=userId -// if successful, otherwise returns an AppError. -func (c *Client) AddUserToTeam(teamId string, userId string) (*Result, *AppError) { - if len(teamId) == 0 { - teamId = c.GetTeamId() - } - - data := make(map[string]string) - data["user_id"] = userId - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/add_user_to_team", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// AddUserToTeamFromInvite adds a user to a team based off data provided in an invite link. -// Either token and data are required or inviteId is required. -func (c *Client) AddUserToTeamFromInvite(token, inviteData, inviteId string) (*Result, *AppError) { - data := make(map[string]string) - data["token"] = token - data["data"] = inviteData - data["invite_id"] = inviteId - if r, err := c.DoApiPost("/teams/add_user_to_team_from_invite", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// Removes a user directly from the team. -// The teamId and userId are required. You must be a valid member of the team and/or -// have the correct role to remove a user from the team. Returns a map of user_id=userId -// if successful, otherwise returns an AppError. -func (c *Client) RemoveUserFromTeam(teamId string, userId string) (*Result, *AppError) { - if len(teamId) == 0 { - teamId = c.GetTeamId() - } - - data := make(map[string]string) - data["user_id"] = userId - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/remove_user_from_team", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), InvitesFromJson(r.Body)}, nil - } -} - -// UpdateTeam updates a team based on the changes in the provided team struct. On success -// it returns a sanitized version of the updated team. Must be authenticated as a team admin -// for that team or a system admin. -func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// User Routes Section - -// CreateUser creates a user in the system based on the provided user struct. -func (c *Client) CreateUser(user *User, token string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// CreateUserWithInvite creates a user based on the provided user struct. Either the token and -// data strings or the inviteId is required from the invite. -func (c *Client) CreateUserWithInvite(user *User, token string, data string, inviteId string) (*Result, *AppError) { - - url := "/users/create?d=" + url.QueryEscape(data) + "&t=" + url.QueryEscape(token) + "&iid=" + url.QueryEscape(inviteId) - - if r, err := c.DoApiPost(url, user.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateUserFromSignup(user *User, data string, token string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/create?d="+url.QueryEscape(data)+"&t="+token, user.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// GetUser returns a user based on a provided user id string. Must be authenticated. -func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// getByUsername returns a user based on a provided username string. Must be authenticated. -func (c *Client) GetByUsername(username string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/users/name/%v", username), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// getByEmail returns a user based on a provided username string. Must be authenticated. -func (c *Client) GetByEmail(email string, etag string) (*User, *ResponseMetadata) { - if r, err := c.DoApiGet(fmt.Sprintf("/users/email/%v", email), "", etag); err != nil { - return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - defer closeBody(r) - return UserFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -// GetMe returns the current user. -func (c *Client) GetMe(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/me", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// GetProfiles returns a map of users using user id as the key. Must be authenticated. -func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesInTeam returns a map of users for a team using user id as the key. Must -// be authenticated. -func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesInChannel returns a map of users for a channel using user id as the key. Must -// be authenticated. -func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must -// be authenticated. -func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesByIds returns a map of users based on the user ids provided. Must -// be authenticated. -func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// SearchUsers returns a list of users that have a username matching or similar to the search term. Must -// be authenticated. -func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil - } -} - -// AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel", -// specifies users in the channel. The second list "out_of_channel" specifies users outside of the -// channel. Term, the string to search against, is required, channel id is also required. Must be authenticated. -func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) { - url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil - } -} - -// AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies -// the users in the team that match the provided term, matching against username, full name and -// nickname. Must be authenticated. -func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) { - url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil - } -} - -// AutocompleteUsers returns a list for autocompletion of users on the system that match the provided term, -// matching against username, full name and nickname. Must be authenticated. -func (c *Client) AutocompleteUsers(term string) (*Result, *AppError) { - url := fmt.Sprintf("/users/autocomplete?term=%s", url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil - } -} - -// LoginById authenticates a user by user id and password. -func (c *Client) LoginById(id string, password string) (*Result, *AppError) { - m := make(map[string]string) - m["id"] = id - m["password"] = password - return c.login(m) -} - -// Login authenticates a user by login id, which can be username, email or some sort -// of SSO identifier based on configuration, and a password. -func (c *Client) Login(loginId string, password string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - return c.login(m) -} - -// LoginByLdap authenticates a user by LDAP id and password. -func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - m["ldap_only"] = "true" - return c.login(m) -} - -// LoginWithDevice authenticates a user by login id (username, email or some sort -// of SSO identifier based on configuration), password and attaches a device id to -// the session. -func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - m["device_id"] = deviceId - return c.login(m) -} - -func (c *Client) login(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil { - return nil, err - } else { - c.AuthToken = r.Header.Get(HEADER_TOKEN) - c.AuthType = HEADER_BEARER - sessionToken := getCookie(SESSION_COOKIE_TOKEN, r) - - if c.AuthToken != sessionToken.Value { - NewAppError("/users/login", "model.client.login.app_error", nil, "", 0) - } - - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// Logout terminates the current user's session. -func (c *Client) Logout() (*Result, *AppError) { - if r, err := c.DoApiPost("/users/logout", ""); err != nil { - return nil, err - } else { - c.AuthToken = "" - c.AuthType = HEADER_BEARER - c.TeamId = "" - - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// CheckMfa returns a map with key "mfa_required" with the string value "true" or "false", -// indicating whether MFA is required to log the user in, based on a provided login id -// (username, email or some sort of SSO identifier based on configuration). -func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - - if r, err := c.DoApiPost("/users/mfa", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GenerateMfaSecret returns a QR code image containing the secret, to be scanned -// by a multi-factor authentication mobile application. It also returns the secret -// for manual entry. Must be authenticated. -func (c *Client) GenerateMfaSecret() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/generate_mfa_secret", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// UpdateMfa activates multi-factor authenticates for the current user if activate -// is true and a valid token is provided. If activate is false, then token is not -// required and multi-factor authentication is disabled for the current user. -func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { - m := make(map[string]interface{}) - m["activate"] = activate - m["token"] = token - - if r, err := c.DoApiPost("/users/update_mfa", StringInterfaceToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) AdminResetMfa(userId string) (*Result, *AppError) { - m := make(map[string]string) - m["user_id"] = userId - - if r, err := c.DoApiPost("/admin/reset_mfa", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) { - m := make(map[string]string) - m["id"] = sessionAltId - - if r, err := c.DoApiPost("/users/revoke_session", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetSessions(id string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/"+id+"/sessions", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), SessionsFromJson(r.Body)}, nil - } -} - -func (c *Client) EmailToOAuth(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/email_to_oauth", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) OAuthToEmail(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/oauth_to_email", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) LDAPToEmail(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) Command(channelId string, command string) (*Result, *AppError) { - args := &CommandArgs{ChannelId: channelId, Command: command} - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", args.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - - response, _ := CommandResponseFromJson(r.Body) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), response}, nil - } -} - -func (c *Client) ListCommands() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil - } -} - -func (c *Client) ListTeamCommands() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list_team_commands", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/create", cmd.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateCommand(cmd *Command) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/update", cmd.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil - } -} - -func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil - } -} - -func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetLogs() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil - } -} - -func (c *Client) GetClusterStatus() ([]*ClusterInfo, *AppError) { - if r, err := c.DoApiGet("/admin/cluster_status", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return ClusterInfosFromJson(r.Body), nil - } -} - -// GetRecentlyActiveUsers returns a map of users including lastActivityAt using user id as the key -func (c *Client) GetRecentlyActiveUsers(teamId string) (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/recently_active_users/"+teamId, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAllAudits() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetConfig() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/config", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ConfigFromJson(r.Body)}, nil - } -} - -// ReloadConfig will reload the config.json file from disk. Properties -// requiring a server restart will still need a server restart. You must -// have the system admin role to call this method. It will return status=OK -// if it's successfully reloaded the config file, otherwise check the returned error. -func (c *Client) ReloadConfig() (bool, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet("/admin/reload_config", "", ""); err != nil { - return false, err - } else { - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) InvalidateAllCaches() (bool, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet("/admin/invalidate_all_caches", "", ""); err != nil { - return false, err - } else { - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) SaveConfig(config *Config) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// RecycleDatabaseConnection will attempt to recycle the database connections. -// You must have the system admin role to call this method. It will return status=OK -// if it's successfully recycled the connections, otherwise check the returned error. -func (c *Client) RecycleDatabaseConnection() (bool, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet("/admin/recycle_db_conn", "", ""); err != nil { - return false, err - } else { - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) TestEmail(config *Config) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/test_email", config.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// TestLdap will run a connection test on the current LDAP settings. -// It will return the standard OK response if settings work. Otherwise -// it will return an appropriate error. -func (c *Client) TestLdap(config *Config) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/ldap_test", config.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetComplianceReports() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CompliancesFromJson(r.Body)}, nil - } -} - -func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/save_compliance_report", job.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ComplianceFromJson(r.Body)}, nil - } -} - -func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) { - var rq *http.Request - rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError("/admin/download_compliance_report", "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - defer rp.Body.Close() - return nil, AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - return &Result{rp.Header.Get(HEADER_REQUEST_ID), - rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil - } -} - -func (c *Client) GetTeamAnalytics(teamId, name string) (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/analytics/"+name, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil - } -} - -// Initiate immediate synchronization of LDAP users. -// The synchronization will be performed asynchronously and this function will -// always return OK unless you don't have permissions. -// You must be the system administrator to use this function. -func (c *Client) LdapSyncNow() (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/ldap_sync_now", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create", channel.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateDirectChannel(userId string) (*Result, *AppError) { - data := make(map[string]string) - data["user_id"] = userId - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_direct", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateGroupChannel(userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_group", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update", channel.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_header", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_purpose", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_notify_props", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetMyChannelMembers() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/members", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannel(id, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelDataFromJson(r.Body)}, nil - } -} - -// GetMoreChannelsPage will return a page of open channels the user is not in based on -// the provided offset and limit. Must be authenticated. -func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetTeamRoute()+"/channels/more/%v/%v", offset, limit), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -// SearchMoreChannels will return a list of open channels the user is not in, that matches -// the search criteria provided. Must be authenticated. -func (c *Client) SearchMoreChannels(channelSearch ChannelSearch) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/more/search", channelSearch.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -// AutocompleteChannels will return a list of open channels that match the provided -// string. Must be authenticated. -func (c *Client) AutocompleteChannels(term string) (*Result, *AppError) { - url := fmt.Sprintf("%s/channels/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelCountsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannels(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannelByName(channelName string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelNameRoute(channelName), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) JoinChannel(id string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) JoinChannelByName(name string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelNameRoute(name)+"/join", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) LeaveChannel(id string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/leave", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) DeleteChannel(id string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/delete", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) { - data := make(map[string]string) - data["user_id"] = user_id - if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/add", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) { - data := make(map[string]string) - data["user_id"] = user_id - if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/remove", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -// ViewChannel performs all the actions related to viewing a channel. This includes marking -// the channel and the previous one as read, and marking the channel as being actively viewed. -// ChannelId is required but may be blank to indicate no channel is being viewed. -// PrevChannelId is optional, populate to indicate a channel switch occurred. -func (c *Client) ViewChannel(params ChannelView) (bool, *ResponseMetadata) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/view", params.ToJson()); err != nil { - return false, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - return c.CheckStatusOK(r), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil - } -} - -// GetChannelMembersByIds will return channel member objects as an array based on the -// channel id and a list of user ids provided. Must be authenticated. -func (c *Client) GetChannelMembersByIds(channelId string, userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/members/ids", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil - } -} - -func (c *Client) CreatePost(post *Post) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdatePost(post *Post) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/update", post.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/page/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/since/%v", time), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPostsBefore(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/before/%v/%v", postid, offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/posts/%v/after/%v/%v", postid, offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get", postId), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -// GetPostById returns a post and any posts in the same thread by post id -func (c *Client) GetPostById(postId string, etag string) (*PostList, *ResponseMetadata) { - if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/%v", postId), "", etag); err != nil { - return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - defer closeBody(r) - return PostListFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -// GetPermalink returns a post list, based on the provided channel and post ID. -func (c *Client) GetPermalink(channelId string, postId string, etag string) (*PostList, *ResponseMetadata) { - if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/pltmp/%v", postId), "", etag); err != nil { - return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - defer closeBody(r) - return PostListFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) { - data := map[string]interface{}{} - data["terms"] = terms - data["is_or_search"] = isOrSearch - if r, err := c.DoApiPost(c.GetTeamRoute()+"/posts/search", StringInterfaceToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -// GetFlaggedPosts will return a post list of posts that have been flagged by the user. -// The page is set by the integer parameters offset and limit. -func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/flagged/%v/%v", offset, limit), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPinnedPosts(channelId string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { - return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) -} - -func (c *Client) UploadPostAttachment(data []byte, channelId string, filename string) (*FileUploadResponse, *AppError) { - c.clearExtraProperties() - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("files", filename); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), 0) - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), 0) - } - - if part, err := writer.CreateFormField("channel_id"); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), 0) - } else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), 0) - } - - if err := writer.Close(); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), 0) - } - - if result, err := c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", body.Bytes(), writer.FormDataContentType()); err != nil { - return nil, err - } else { - return result.Data.(*FileUploadResponse), nil - } -} - -func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) { - rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - return nil, AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - return &Result{rp.Header.Get(HEADER_REQUEST_ID), - rp.Header.Get(HEADER_ETAG_SERVER), FileUploadResponseFromJson(rp.Body)}, nil - } -} - -func (c *Client) GetFile(fileId string) (io.ReadCloser, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get", "", ""); err != nil { - return nil, err - } else { - c.fillInExtraProperties(r) - return r.Body, nil - } -} - -func (c *Client) GetFileThumbnail(fileId string) (io.ReadCloser, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_thumbnail", "", ""); err != nil { - return nil, err - } else { - c.fillInExtraProperties(r) - return r.Body, nil - } -} - -func (c *Client) GetFilePreview(fileId string) (io.ReadCloser, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_preview", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return r.Body, nil - } -} - -func (c *Client) GetFileInfo(fileId string) (*FileInfo, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_info", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return FileInfoFromJson(r.Body), nil - } -} - -func (c *Client) GetPublicLink(fileId string) (string, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_public_link", "", ""); err != nil { - return "", err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return StringFromJson(r.Body), nil - } -} - -func (c *Client) UpdateUser(user *User) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/update", user.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateUserRoles(userId string, roles string) (*Result, *AppError) { - data := make(map[string]string) - data["new_roles"] = roles - - if r, err := c.DoApiPost(c.GetUserRequiredRoute(userId)+"/update_roles", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateTeamRoles(userId string, roles string) (*Result, *AppError) { - data := make(map[string]string) - data["new_roles"] = roles - data["user_id"] = userId - - if r, err := c.DoApiPost(c.GetTeamRoute()+"/update_member_roles", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) { - data := make(map[string]string) - data["device_id"] = deviceId - if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) { - data := make(map[string]string) - data["user_id"] = userId - data["active"] = strconv.FormatBool(active) - if r, err := c.DoApiPost("/users/update_active", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/update_notify", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string) (*Result, *AppError) { - data := make(map[string]string) - data["current_password"] = currentPassword - data["new_password"] = newPassword - data["user_id"] = userId - - if r, err := c.DoApiPost("/users/newpassword", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) SendPasswordReset(email string) (*Result, *AppError) { - data := map[string]string{} - data["email"] = email - if r, err := c.DoApiPost("/users/send_password_reset", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) ResetPassword(code, newPassword string) (*Result, *AppError) { - data := map[string]string{} - data["code"] = code - data["new_password"] = newPassword - if r, err := c.DoApiPost("/users/reset_password", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppError) { - data := map[string]string{} - data["user_id"] = userId - data["new_password"] = newPassword - if r, err := c.DoApiPost("/admin/reset_password", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetStatuses returns a map of string statuses using user id as the key -func (c *Client) GetStatuses() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/status", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetStatusesByIds returns a map of string statuses using user id as the key, -// based on the provided user ids -func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// GetTeamMembers will return a page of team member objects as an array paged based on the -// team id, offset and limit provided. Must be authenticated. -func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil - } -} - -// GetMyTeamMembers will return an array with team member objects that the current user -// is a member of. Must be authenticated. -func (c *Client) GetMyTeamMembers() (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/members", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil - } -} - -// GetMyTeamsUnread will return an array with TeamUnread objects that contain the amount of -// unread messages and mentions the current user has for the teams it belongs to. -// An optional team ID can be set to exclude that team from the results. Must be authenticated. -func (c *Client) GetMyTeamsUnread(teamId string) (*Result, *AppError) { - endpoint := "/teams/unread" - - if teamId != "" { - endpoint += fmt.Sprintf("?id=%s", url.QueryEscape(teamId)) - } - if r, err := c.DoApiGet(endpoint, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamsUnreadFromJson(r.Body)}, nil - } -} - -// GetTeamMember will return a team member object based on the team id and user id provided. -// Must be authenticated. -func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil - } -} - -// GetTeamStats will return a team stats object containing the number of users on the team -// based on the team id provided. Must be authenticated. -func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil - } -} - -// GetTeamByName will return a team object based on the team name provided. Must be authenticated. -func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// GetTeamMembersByIds will return team member objects as an array based on the -// team id and a list of user ids provided. Must be authenticated. -func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil - } -} - -// RegisterApp creates a new OAuth2 app to be used with the OAuth2 Provider. On success -// it returns the created app. Must be authenticated as a user. -func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { - if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil - } -} - -// AllowOAuth allows a new session by an OAuth2 App. On success -// it returns the url to be redirected back to the app which initiated the oauth2 flow. -// Must be authenticated as a user. -func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetOAuthAppsByUser returns the OAuth2 Apps registered by the user. On success -// it returns a list of OAuth2 Apps from the same user or all the registered apps if the user -// is a System Administrator. Must be authenticated as a user. -func (c *Client) GetOAuthAppsByUser() (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil - } -} - -// GetOAuthAppInfo lookup an OAuth2 App using the client_id. On success -// it returns a Sanitized OAuth2 App. Must be authenticated as a user. -func (c *Client) GetOAuthAppInfo(clientId string) (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/app/"+clientId, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil - } -} - -// DeleteOAuthApp deletes an OAuth2 app, the app must be deleted by the same user who created it or -// a System Administrator. On success returs Status OK. Must be authenticated as a user. -func (c *Client) DeleteOAuthApp(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost("/oauth/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetOAuthAuthorizedApps returns the OAuth2 Apps authorized by the user. On success -// it returns a list of sanitized OAuth2 Authorized Apps by the user. -func (c *Client) GetOAuthAuthorizedApps() (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/authorized", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil - } -} - -// OAuthDeauthorizeApp deauthorize a user an OAuth 2.0 app. On success -// it returns status OK or an AppError on fail. -func (c *Client) OAuthDeauthorizeApp(clientId string) *AppError { - if r, err := c.DoApiPost("/oauth/"+clientId+"/deauthorize", ""); err != nil { - return err - } else { - defer closeBody(r) - return nil - } -} - -// RegenerateOAuthAppSecret generates a new OAuth App Client Secret. On success -// it returns an OAuth2 App. Must be authenticated as a user and the same user who -// registered the app or a System Admin. -func (c *Client) RegenerateOAuthAppSecret(clientId string) (*Result, *AppError) { - if r, err := c.DoApiPost("/oauth/"+clientId+"/regen_secret", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { - if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AccessResponseFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/create", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/update", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) PostToWebhook(id, payload string) (*Result, *AppError) { - if r, err := c.DoPost("/hooks/"+id, payload, "application/x-www-form-urlencoded"); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) DeleteIncomingWebhook(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) ListIncomingWebhooks() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/incoming/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAllPreferences() (*Result, *AppError) { - if r, err := c.DoApiGet("/preferences/", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - preferences, _ := PreferencesFromJson(r.Body) - return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil - } -} - -func (c *Client) SetPreferences(preferences *Preferences) (*Result, *AppError) { - if r, err := c.DoApiPost("/preferences/save", preferences.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil - } -} - -func (c *Client) GetPreference(category string, name string) (*Result, *AppError) { - if r, err := c.DoApiGet("/preferences/"+category+"/"+name, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PreferenceFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) { - if r, err := c.DoApiGet("/preferences/"+category, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - preferences, _ := PreferencesFromJson(r.Body) - return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil - } -} - -// DeletePreferences deletes a list of preferences owned by the current user. If successful, -// it will return status=ok. Otherwise, an error will be returned. -func (c *Client) DeletePreferences(preferences *Preferences) (bool, *AppError) { - if r, err := c.DoApiPost("/preferences/delete", preferences.ToJson()); err != nil { - return false, err - } else { - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/update", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) DeleteOutgoingWebhook(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/outgoing/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookListFromJson(r.Body)}, nil - } -} - -func (c *Client) RegenOutgoingWebhookToken(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/regen_token", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) MockSession(sessionToken string) { - c.AuthToken = sessionToken - c.AuthType = HEADER_BEARER -} - -func (c *Client) GetClientLicenceConfig(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/license/client_config", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetInitialLoad() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/initial_load", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil - } -} - -// ListEmoji returns a list of all user-created emoji for the server. -func (c *Client) ListEmoji() ([]*Emoji, *AppError) { - if r, err := c.DoApiGet(c.GetEmojiRoute()+"/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return EmojiListFromJson(r.Body), nil - } -} - -// CreateEmoji will save an emoji to the server if the current user has permission -// to do so. If successful, the provided emoji will be returned with its Id field -// filled in. Otherwise, an error will be returned. -func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoji, *AppError) { - c.clearExtraProperties() - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("image", filename); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0) - } else if _, err = io.Copy(part, bytes.NewBuffer(image)); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0) - } - - if err := writer.WriteField("emoji", emoji.ToJson()); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.emoji.app_error", nil, err.Error(), 0) - } - - if err := writer.Close(); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.writer.app_error", nil, err.Error(), 0) - } - - rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body) - rq.Header.Set("Content-Type", writer.FormDataContentType()) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if r, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.connecting.app_error", nil, err.Error(), 0) - } else if r.StatusCode >= 300 { - return nil, AppErrorFromJson(r.Body) - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return EmojiFromJson(r.Body), nil - } -} - -// DeleteEmoji will delete an emoji from the server if the current user has permission -// to do so. If successful, it will return status=ok. Otherwise, an error will be returned. -func (c *Client) DeleteEmoji(id string) (bool, *AppError) { - data := map[string]string{"id": id} - - if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil { - return false, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -// GetCustomEmojiImageUrl returns the API route that can be used to get the image used by -// the given emoji. -func (c *Client) GetCustomEmojiImageUrl(id string) string { - return c.GetEmojiRoute() + "/" + id -} - -// Uploads a x509 base64 Certificate or Private Key file to be used with SAML. -// data byte array is required and needs to be a Multi-Part with 'certificate' as the field name -// contentType is also required. Returns nil if successful, otherwise returns an AppError -func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppError { - url := c.ApiUrl + "/admin/add_certificate" - rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - return AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - c.fillInExtraProperties(rp) - return nil - } -} - -// Removes a x509 base64 Certificate or Private Key file used with SAML. -// filename is required. Returns nil if successful, otherwise returns an AppError -func (c *Client) RemoveCertificateFile(filename string) *AppError { - if r, err := c.DoApiPost("/admin/remove_certificate", MapToJson(map[string]string{"filename": filename})); err != nil { - return err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return nil - } -} - -// Checks if the x509 base64 Certificates and Private Key files used with SAML exists on the file system. -// Returns a map[string]interface{} if successful, otherwise returns an AppError. Must be System Admin authenticated. -func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, *AppError) { - if r, err := c.DoApiGet("/admin/remove_certificate", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return StringInterfaceFromJson(r.Body), nil - } -} - -// GetWebrtcToken if Successful returns a map with a valid token, stun server and turn server with credentials to use with -// the Mattermost WebRTC service, otherwise returns an AppError. Must be authenticated user. -func (c *Client) GetWebrtcToken() (map[string]string, *AppError) { - if r, err := c.DoApiPost("/webrtc/token", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return MapFromJson(r.Body), nil - } -} - -// GetFileInfosForPost returns a list of FileInfo objects for a given post id, if successful. -// Otherwise, it returns an error. -func (c *Client) GetFileInfosForPost(channelId string, postId string, etag string) ([]*FileInfo, *AppError) { - c.clearExtraProperties() - - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get_file_infos", postId), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return FileInfosFromJson(r.Body), nil - } -} - -// Saves an emoji reaction for a post in the given channel. Returns the saved reaction if successful, otherwise returns an AppError. -func (c *Client) SaveReaction(channelId string, reaction *Reaction) (*Reaction, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/save", reaction.PostId), reaction.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return ReactionFromJson(r.Body), nil - } -} - -// Removes an emoji reaction for a post in the given channel. Returns nil if successful, otherwise returns an AppError. -func (c *Client) DeleteReaction(channelId string, reaction *Reaction) *AppError { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/delete", reaction.PostId), reaction.ToJson()); err != nil { - return err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return nil - } -} - -// Lists all emoji reactions made for the given post in the given channel. Returns a list of Reactions if successful, otherwise returns an AppError. -func (c *Client) ListReactions(channelId string, postId string) ([]*Reaction, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions", postId), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return ReactionsFromJson(r.Body), nil - } -} - -// Updates the user's roles in the channel by replacing them with the roles provided. -func (c *Client) UpdateChannelRoles(channelId string, userId string, roles string) (map[string]string, *ResponseMetadata) { - data := make(map[string]string) - data["new_roles"] = roles - data["user_id"] = userId - - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_member_roles", MapToJson(data)); err != nil { - metadata := ResponseMetadata{Error: err} - if r != nil { - metadata.StatusCode = r.StatusCode - } - return nil, &metadata - } else { - defer closeBody(r) - return MapFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -func (c *Client) PinPost(channelId string, postId string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/pin", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} - -func (c *Client) UnpinPost(channelId string, postId string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/unpin", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} diff --git a/model/client4.go b/model/client4.go index 7ca2bdf4e..d245fe6c0 100644 --- a/model/client4.go +++ b/model/client4.go @@ -13,6 +13,33 @@ import ( "net/url" "strconv" "strings" + "time" +) + +const ( + HEADER_REQUEST_ID = "X-Request-ID" + HEADER_VERSION_ID = "X-Version-ID" + HEADER_CLUSTER_ID = "X-Cluster-ID" + HEADER_ETAG_SERVER = "ETag" + HEADER_ETAG_CLIENT = "If-None-Match" + HEADER_FORWARDED = "X-Forwarded-For" + HEADER_REAL_IP = "X-Real-IP" + HEADER_FORWARDED_PROTO = "X-Forwarded-Proto" + HEADER_TOKEN = "token" + HEADER_BEARER = "BEARER" + HEADER_AUTH = "Authorization" + HEADER_REQUESTED_WITH = "X-Requested-With" + HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" + STATUS = "status" + STATUS_OK = "OK" + STATUS_FAIL = "FAIL" + STATUS_REMOVE = "REMOVE" + + CLIENT_DIR = "client" + + API_URL_SUFFIX_V1 = "/api/v1" + API_URL_SUFFIX_V4 = "/api/v4" + API_URL_SUFFIX = API_URL_SUFFIX_V4 ) type Response struct { @@ -32,6 +59,24 @@ type Client4 struct { AuthType string } +func closeBody(r *http.Response) { + if r.Body != nil { + ioutil.ReadAll(r.Body) + r.Body.Close() + } +} + +// Must is a convenience function used for testing. +func (c *Client4) Must(result interface{}, resp *Response) interface{} { + if resp.Error != nil { + + time.Sleep(time.Second) + panic(resp.Error) + } + + return result +} + func NewAPIv4Client(url string) *Client4 { return &Client4{url, url + API_URL_SUFFIX, &http.Client{}, "", ""} } @@ -64,6 +109,11 @@ func BuildResponse(r *http.Response) *Response { } } +func (c *Client4) MockSession(sessionToken string) { + c.AuthToken = sessionToken + c.AuthType = HEADER_BEARER +} + func (c *Client4) SetOAuthToken(token string) { c.AuthToken = token c.AuthType = HEADER_TOKEN @@ -2966,6 +3016,28 @@ func (c *Client4) DeauthorizeOAuthApp(appId string) (bool, *Response) { } } +// GetOAuthAccessToken is a test helper function for the OAuth access token endpoint. +func (c *Client4) GetOAuthAccessToken(data url.Values) (*AccessResponse, *Response) { + rq, _ := http.NewRequest(http.MethodPost, c.Url+"/oauth/access_token", strings.NewReader(data.Encode())) + rq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rq.Close = true + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.Url+"/oauth/access_token", "model.client.connecting.app_error", nil, err.Error(), 403)} + } else { + defer closeBody(rp) + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return AccessResponseFromJson(rp.Body), BuildResponse(rp) + } + } +} + // Elasticsearch Section // TestElasticsearch will attempt to connect to the configured Elasticsearch server and return OK if configured diff --git a/model/config.go b/model/config.go index 07cd9d977..a5a588a82 100644 --- a/model/config.go +++ b/model/config.go @@ -184,7 +184,6 @@ type ServiceSettings struct { EnableOnlyAdminIntegrations *bool EnablePostUsernameOverride bool EnablePostIconOverride bool - EnableAPIv3 *bool EnableLinkPreviews *bool EnableTesting bool EnableDeveloper *bool @@ -244,10 +243,6 @@ func (s *ServiceSettings) SetDefaults() { s.ListenAddress = NewString(SERVICE_SETTINGS_DEFAULT_LISTEN_AND_ADDRESS) } - if s.EnableAPIv3 == nil { - s.EnableAPIv3 = NewBool(true) - } - if s.EnableLinkPreviews == nil { s.EnableLinkPreviews = NewBool(false) } diff --git a/model/websocket_client.go b/model/websocket_client.go index 788dbee20..4e6c1d8cc 100644 --- a/model/websocket_client.go +++ b/model/websocket_client.go @@ -39,15 +39,15 @@ func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { // NewWebSocketClientWithDialer constructs a new WebSocket client with convenience // methods for talking to the server using a custom dialer. func NewWebSocketClientWithDialer(dialer *websocket.Dialer, url, authToken string) (*WebSocketClient, *AppError) { - conn, _, err := dialer.Dial(url+API_URL_SUFFIX_V3+"/users/websocket", nil) + conn, _, err := dialer.Dial(url+API_URL_SUFFIX+"/websocket", nil) if err != nil { return nil, NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) } client := &WebSocketClient{ url, - url + API_URL_SUFFIX_V3, - url + API_URL_SUFFIX_V3 + "/users/websocket", + url + API_URL_SUFFIX, + url + API_URL_SUFFIX + "/websocket", conn, authToken, 1, @@ -74,30 +74,7 @@ func NewWebSocketClient4(url, authToken string) (*WebSocketClient, *AppError) { // NewWebSocketClient4WithDialer constructs a new WebSocket client with convenience // methods for talking to the server using a custom dialer. Uses the v4 endpoint. func NewWebSocketClient4WithDialer(dialer *websocket.Dialer, url, authToken string) (*WebSocketClient, *AppError) { - conn, _, err := dialer.Dial(url+API_URL_SUFFIX+"/websocket", nil) - if err != nil { - return nil, NewAppError("NewWebSocketClient4", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - client := &WebSocketClient{ - url, - url + API_URL_SUFFIX, - url + API_URL_SUFFIX + "/websocket", - conn, - authToken, - 1, - make(chan bool, 1), - make(chan *WebSocketEvent, 100), - make(chan *WebSocketResponse, 100), - nil, - nil, - } - - client.configurePingHandling() - - client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) - - return client, nil + return NewWebSocketClientWithDialer(dialer, url, authToken) } func (wsc *WebSocketClient) Connect() *AppError { diff --git a/utils/config.go b/utils/config.go index 3827cf4ee..1b5fc7674 100644 --- a/utils/config.go +++ b/utils/config.go @@ -448,7 +448,6 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L props["WebsocketURL"] = strings.TrimRight(*c.ServiceSettings.WebsocketURL, "/") props["SiteName"] = c.TeamSettings.SiteName props["EnableTeamCreation"] = strconv.FormatBool(*c.TeamSettings.EnableTeamCreation) - props["EnableAPIv3"] = strconv.FormatBool(*c.ServiceSettings.EnableAPIv3) props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation) props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer) props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage diff --git a/web/web_test.go b/web/web_test.go index 4497f00cc..9b6230013 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -17,7 +17,7 @@ import ( "github.com/mattermost/mattermost-server/utils" ) -var ApiClient *model.Client +var ApiClient *model.Client4 var URL string type persistentTestStore struct { @@ -58,7 +58,7 @@ func Setup() *TestHelper { NewWeb(a, a.Srv.Router) URL = fmt.Sprintf("http://localhost:%v", a.Srv.ListenAddr.Port) - ApiClient = model.NewClient(URL) + ApiClient = model.NewAPIv4Client(URL) a.DoAdvancedPermissionsMigration() @@ -66,7 +66,6 @@ func Setup() *TestHelper { a.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true - *cfg.ServiceSettings.EnableAPIv3 = true }) th := &TestHelper{ diff --git a/web/webhook_test.go b/web/webhook_test.go index e625e55bb..48e0a2744 100644 --- a/web/webhook_test.go +++ b/web/webhook_test.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "net/http" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -20,7 +21,7 @@ func TestIncomingWebhook(t *testing.T) { defer th.TearDown() if !th.App.Config().ServiceSettings.EnableIncomingWebhooks { - _, err := ApiClient.PostToWebhook("123", "123") + _, err := http.Post(ApiClient.Url+"/hooks/123", "", strings.NewReader("123")) assert.NotNil(t, err, "should have errored - webhooks turned off") return } @@ -28,7 +29,7 @@ func TestIncomingWebhook(t *testing.T) { hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id}) require.Nil(t, err) - url := "/hooks/" + hook.Id + url := ApiClient.Url + "/hooks/" + hook.Id tooLongText := "" for i := 0; i < 8200; i++ { @@ -37,55 +38,68 @@ func TestIncomingWebhook(t *testing.T) { t.Run("WebhookBasics", func(t *testing.T) { payload := "payload={\"text\": \"test text\"}" - _, err := ApiClient.DoPost(url, payload, "application/x-www-form-urlencoded") - assert.Nil(t, err) + resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) payload = "payload={\"text\": \"\"}" - _, err = ApiClient.DoPost(url, payload, "application/x-www-form-urlencoded") - assert.NotNil(t, err, "should have errored - no text to post") + resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) + require.Nil(t, err) + assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - no text to post") payload = "payload={\"text\": \"test text\", \"channel\": \"junk\"}" - _, err = ApiClient.DoPost(url, payload, "application/x-www-form-urlencoded") - assert.NotNil(t, err, "should have errored - bad channel") + resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload)) + require.Nil(t, err) + assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad channel") payload = "payload={\"text\": \"test text\"}" - _, err = ApiClient.DoPost("/hooks/abc123", payload, "application/x-www-form-urlencoded") - assert.NotNil(t, err, "should have errored - bad hook") + resp, err = http.Post(ApiClient.Url+"/hooks/abc123", "application/x-www-form-urlencoded", strings.NewReader(payload)) + require.Nil(t, err) + assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad hook") - _, err = ApiClient.DoPost(url, "{\"text\":\"this is a test\"}", "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}")) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) text := `this is a \"test\" that contains a newline and a tab` - _, err = ApiClient.DoPost(url, "{\"text\":\""+text+"\"}", "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+text+"\"}")) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) - _, err = ApiClient.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name), "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name))) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) - _, err = ApiClient.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"#%s\"}", th.BasicChannel.Name), "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"#%s\"}", th.BasicChannel.Name))) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) - _, err = ApiClient.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"@%s\"}", th.BasicUser.Username), "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"@%s\"}", th.BasicUser.Username))) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) - _, err = ApiClient.DoPost(url, "payload={\"text\":\"this is a test\"}", "application/x-www-form-urlencoded") - assert.Nil(t, err) + resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\"this is a test\"}")) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) - _, err = ApiClient.DoPost(url, "payload={\"text\":\""+text+"\"}", "application/x-www-form-urlencoded") + resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}")) assert.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) - _, err = ApiClient.DoPost(url, "{\"text\":\""+tooLongText+"\"}", "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+tooLongText+"\"}")) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) payloadMultiPart := "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"username\"\r\n\r\nwebhook-bot\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nthis is a test :tada:\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--" - _, err = ApiClient.DoPost("/hooks/"+hook.Id, payloadMultiPart, "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW") - assert.Nil(t, err) + resp, err = http.Post(ApiClient.Url+"/hooks/"+hook.Id, "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", strings.NewReader(payloadMultiPart)) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) }) t.Run("WebhookExperimentReadOnly", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = false }) - _, err := ApiClient.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL), "application/json") + _, err := http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL))) assert.Nil(t, err, "Not read only") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) @@ -126,8 +140,9 @@ func TestIncomingWebhook(t *testing.T) { ] }` - _, err := ApiClient.DoPost(url, attachmentPayload, "application/json") - assert.Nil(t, err) + resp, err := http.Post(url, "application/json", strings.NewReader(attachmentPayload)) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) attachmentPayload = `{ "text": "this is a test", @@ -162,14 +177,16 @@ func TestIncomingWebhook(t *testing.T) { ] }` - _, err = ApiClient.DoPost(url, attachmentPayload, "application/json") - assert.Nil(t, err) + resp, err = http.Post(url, "application/json", strings.NewReader(attachmentPayload)) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusOK) }) t.Run("DisableWebhooks", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = false }) - _, err := ApiClient.DoPost(url, "{\"text\":\"this is a test\"}", "application/json") - assert.NotNil(t, err) + resp, err := http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}")) + require.Nil(t, err) + assert.True(t, resp.StatusCode == http.StatusNotImplemented) }) } -- cgit v1.2.3-1-g7c22