diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/admin_test.go | 8 | ||||
-rw-r--r-- | api/channel.go | 53 | ||||
-rw-r--r-- | api/channel_test.go | 7 | ||||
-rw-r--r-- | api/command.go | 73 | ||||
-rw-r--r-- | api/command_test.go | 56 | ||||
-rw-r--r-- | api/context.go | 2 | ||||
-rw-r--r-- | api/post.go | 85 | ||||
-rw-r--r-- | api/server.go | 10 | ||||
-rw-r--r-- | api/team.go | 33 | ||||
-rw-r--r-- | api/team_test.go | 40 | ||||
-rw-r--r-- | api/templates/signup_team_subject.html | 2 | ||||
-rw-r--r-- | api/user.go | 75 | ||||
-rw-r--r-- | api/user_test.go | 39 |
13 files changed, 451 insertions, 32 deletions
diff --git a/api/admin_test.go b/api/admin_test.go index 0db5caa4c..0a1682a99 100644 --- a/api/admin_test.go +++ b/api/admin_test.go @@ -235,6 +235,10 @@ func TestGetPostCount(t *testing.T) { post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"} post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) + // manually update creation time, since it's always set to 0 upon saving and we only retrieve posts < today + Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId", + map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())}) + if _, err := Client.GetAnalytics(team.Id, "post_counts_day"); err == nil { t.Fatal("Shouldn't have permissions") } @@ -276,6 +280,10 @@ func TestUserCountsWithPostsByDay(t *testing.T) { post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"} post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) + // manually update creation time, since it's always set to 0 upon saving and we only retrieve posts < today + Srv.Store.(*store.SqlStore).GetMaster().Exec("UPDATE Posts SET CreateAt = :CreateAt WHERE ChannelId = :ChannelId", + map[string]interface{}{"ChannelId": channel1.Id, "CreateAt": utils.MillisFromTime(utils.Yesterday())}) + if _, err := Client.GetAnalytics(team.Id, "user_counts_with_posts_day"); err == nil { t.Fatal("Shouldn't have permissions") } diff --git a/api/channel.go b/api/channel.go index 44be1cf97..f17594c0a 100644 --- a/api/channel.go +++ b/api/channel.go @@ -205,9 +205,11 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { } if oldChannel.Name == model.DEFAULT_CHANNEL { - c.Err = model.NewAppError("updateChannel", "Cannot update the default channel "+model.DEFAULT_CHANNEL, "") - c.Err.StatusCode = http.StatusForbidden - return + if (len(channel.Name) > 0 && channel.Name != oldChannel.Name) || (len(channel.Type) > 0 && channel.Type != oldChannel.Type) { + c.Err = model.NewAppError("updateChannel", "Tried to perform an invalid update of the default channel "+model.DEFAULT_CHANNEL, "") + c.Err.StatusCode = http.StatusForbidden + return + } } oldChannel.Header = channel.Header @@ -266,19 +268,51 @@ func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) { if !c.HasPermissionsToTeam(channel.TeamId, "updateChannelHeader") { return } - + oldChannelHeader := channel.Header channel.Header = channelHeader if ucresult := <-Srv.Store.Channel().Update(channel); ucresult.Err != nil { c.Err = ucresult.Err return } else { + PostUpdateChannelHeaderMessageAndForget(c, channel.Id, oldChannelHeader, channelHeader) c.LogAudit("name=" + channel.Name) w.Write([]byte(channel.ToJson())) } } } +func PostUpdateChannelHeaderMessageAndForget(c *Context, channelId string, oldChannelHeader, newChannelHeader string) { + go func() { + uc := Srv.Store.User().Get(c.Session.UserId) + + if uresult := <-uc; uresult.Err != nil { + l4g.Error("Failed to retrieve user while trying to save update channel header message %v", uresult.Err) + return + } else { + user := uresult.Data.(*model.User) + + var message string + if oldChannelHeader == "" { + message = fmt.Sprintf("%s updated the channel header to: %s", user.Username, newChannelHeader) + } else if newChannelHeader == "" { + message = fmt.Sprintf("%s removed the channel header (was: %s)", user.Username, oldChannelHeader) + } else { + message = fmt.Sprintf("%s updated the channel header from: %s to: %s", user.Username, oldChannelHeader, newChannelHeader) + } + + post := &model.Post{ + ChannelId: channelId, + Message: message, + Type: model.POST_HEADER_CHANGE, + } + if _, err := CreatePost(c, post, false); err != nil { + l4g.Error("Failed to post join/leave message %v", err) + } + } + }() +} + func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) channelId := props["channel_id"] @@ -419,7 +453,7 @@ func JoinChannel(c *Context, channelId string, role string) { c.Err = err return } - PostUserAddRemoveMessageAndForget(c, channel.Id, fmt.Sprintf(`User %v has joined this channel.`, user.Username)) + PostUserAddRemoveMessageAndForget(c, channel.Id, fmt.Sprintf(`%v has joined the channel.`, user.Username)) } else { c.Err = model.NewAppError("join", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden @@ -706,7 +740,8 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { } scm := Srv.Store.Channel().GetMember(id, c.Session.UserId) - ecm := Srv.Store.Channel().GetExtraMembers(id, 20) + ecm := Srv.Store.Channel().GetExtraMembers(id, 100) + ccm := Srv.Store.Channel().GetMemberCount(id) if cmresult := <-scm; cmresult.Err != nil { c.Err = cmresult.Err @@ -714,9 +749,13 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { } else if ecmresult := <-ecm; ecmresult.Err != nil { c.Err = ecmresult.Err return + } else if ccmresult := <-ccm; ccmresult.Err != nil { + c.Err = ccmresult.Err + return } else { member := cmresult.Data.(model.ChannelMember) extraMembers := ecmresult.Data.([]model.ExtraMember) + memberCount := ccmresult.Data.(int64) if !c.HasPermissionsToTeam(channel.TeamId, "getChannelExtraInfo") { return @@ -732,7 +771,7 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { return } - data := model.ChannelExtra{Id: channel.Id, Members: extraMembers} + data := model.ChannelExtra{Id: channel.Id, Members: extraMembers, MemberCount: memberCount} w.Header().Set(model.HEADER_ETAG_SERVER, extraEtag) w.Header().Set("Expires", "-1") w.Write([]byte(data.ToJson())) diff --git a/api/channel_test.go b/api/channel_test.go index a41f63b1b..e7e1f4eb0 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -215,8 +215,9 @@ func TestUpdateChannel(t *testing.T) { for _, c := range data.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") + t.Fatal("should have errored on updating default channel name") } break } @@ -677,6 +678,10 @@ func TestGetChannelExtraInfo(t *testing.T) { data := rget.Data.(*model.ChannelExtra) if data.Id != channel1.Id { t.Fatal("couldnt't get extra info") + } else if len(data.Members) != 1 { + t.Fatal("got incorrect members") + } else if data.MemberCount != 1 { + t.Fatal("got incorrect member count") } // diff --git a/api/command.go b/api/command.go index 50ca41155..db57f0bae 100644 --- a/api/command.go +++ b/api/command.go @@ -4,7 +4,9 @@ package api import ( + "io" "net/http" + "path" "strconv" "strings" "time" @@ -325,6 +327,9 @@ func loadTestCommand(c *Context, command *model.Command) bool { if loadTestPostsCommand(c, command) { return true } + if loadTestUrlCommand(c, command) { + return true + } } else if strings.Index(cmd, command.Command) == 0 { command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Debug Load Testing"}) } @@ -571,3 +576,71 @@ func loadTestPostsCommand(c *Context, command *model.Command) bool { return false } + +func loadTestUrlCommand(c *Context, command *model.Command) bool { + cmd := cmds["loadTestCommand"] + " url" + + if strings.Index(command.Command, cmd) == 0 && !command.Suggest { + url := "" + + parameters := strings.SplitN(command.Command, " ", 3) + if len(parameters) != 3 { + c.Err = model.NewAppError("loadTestUrlCommand", "Command must contain a url", "") + return true + } else { + url = parameters[2] + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/doc/developer/tests/" + url + + if path.Ext(url) == "" { + url += ".md" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", err.Error()) + return false + } else if r.StatusCode > 400 { + c.Err = model.NewAppError("loadTestUrlCommand", "Unable to get file", r.Status) + return false + } else { + contents = r.Body + } + + bytes := make([]byte, 4000) + + // break contents into 4000 byte posts + for { + length, err := contents.Read(bytes) + if err != nil && err != io.EOF { + c.Err = model.NewAppError("loadTestUrlCommand", "Encountered error reading file", err.Error()) + return false + } + + if length == 0 { + break + } + + post := &model.Post{} + post.Message = string(bytes[:length]) + post.ChannelId = command.ChannelId + + if _, err := CreatePost(c, post, false); err != nil { + l4g.Error("Unable to create post, err=%v", err) + return false + } + } + + command.Response = model.RESP_EXECUTED + + return true + } else if strings.Index(cmd, command.Command) == 0 && strings.Index(command.Command, "/loadtest posts") != 0 { + command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Add a post containing the text from a given url to current channel <Url>"}) + } + + return false +} diff --git a/api/command_test.go b/api/command_test.go index 476748c6b..9327850f3 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -10,6 +10,7 @@ import ( "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" ) func TestSuggestRootCommands(t *testing.T) { @@ -184,3 +185,58 @@ func TestEchoCommand(t *testing.T) { t.Fatal("Echo command failed to send") } } + +func TestLoadTestUrlCommand(t *testing.T) { + Setup() + + // enable testing to use /loadtest but don't save it since we don't want to overwrite config.json + enableTesting := utils.Cfg.ServiceSettings.EnableTesting + defer func() { + utils.Cfg.ServiceSettings.EnableTesting = enableTesting + }() + + utils.Cfg.ServiceSettings.EnableTesting = true + + 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{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + channel := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) + + command := "/loadtest url " + if _, err := Client.Command(channel.Id, command, false); err == nil { + t.Fatal("/loadtest url with no url should've failed") + } + + command = "/loadtest url http://www.hopefullynonexistent.file/path/asdf/qwerty" + if _, err := Client.Command(channel.Id, command, false); err == nil { + t.Fatal("/loadtest url with invalid url should've failed") + } + + command = "/loadtest url https://raw.githubusercontent.com/mattermost/platform/master/README.md" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { + t.Fatal("/loadtest url for README.md should've executed") + } + + command = "/loadtest url test-emoticons.md" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { + t.Fatal("/loadtest url for test-emoticons.md should've executed") + } + + command = "/loadtest url test-emoticons" + if r := Client.Must(Client.Command(channel.Id, command, false)).Data.(*model.Command); r.Response != model.RESP_EXECUTED { + t.Fatal("/loadtest url for test-emoticons should've executed") + } + + posts := Client.Must(Client.GetPosts(channel.Id, 0, 5, "")).Data.(*model.PostList) + // note that this may make more than 3 posts if files are too long to fit in an individual post + if len(posts.Order) < 3 { + t.Fatal("/loadtest url made too few posts, perhaps there needs to be a delay before GetPosts in the test?") + } +} diff --git a/api/context.go b/api/context.go index a5d4169cb..a6f9bc1e1 100644 --- a/api/context.go +++ b/api/context.go @@ -37,6 +37,8 @@ type Page struct { ClientCfg map[string]string User *model.User Team *model.Team + Channel *model.Channel + PostID string SessionTokenIndex int64 } diff --git a/api/post.go b/api/post.go index ffb69f382..5fbacc906 100644 --- a/api/post.go +++ b/api/post.go @@ -23,6 +23,7 @@ func InitPost(r *mux.Router) { l4g.Debug("Initializing post api routes") r.Handle("/posts/search", ApiUserRequired(searchPosts)).Methods("GET") + r.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET") sr := r.PathPrefix("/channels/{id:[A-Za-z0-9]+}").Subrouter() sr.Handle("/create", ApiUserRequired(createPost)).Methods("POST") @@ -152,9 +153,6 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") - linkRegex := regexp.MustCompile(`<\s*(\S*)\s*>`) - text = linkRegex.ReplaceAllString(text, "${1}") - post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text, Type: postType} post.AddProp("from_webhook", "true") @@ -176,7 +174,21 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc if len(props) > 0 { for key, val := range props { - if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { + if key == "attachments" { + if list, success := val.([]interface{}); success { + // parse attachment links into Markdown format + for i, aInt := range list { + attachment := aInt.(map[string]interface{}) + if _, ok := attachment["text"]; ok { + aText := attachment["text"].(string) + aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})") + attachment["text"] = aText + list[i] = attachment + } + } + post.AddProp(key, list) + } + } else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { post.AddProp(key, val) } } @@ -406,9 +418,9 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, } // Add @all to keywords if user has them turned on - if profile.NotifyProps["all"] == "true" { - keywordMap["@all"] = append(keywordMap["@all"], profile.Id) - } + // if profile.NotifyProps["all"] == "true" { + // keywordMap["@all"] = append(keywordMap["@all"], profile.Id) + // } // Add @channel to keywords if user has them turned on if profile.NotifyProps["channel"] == "true" { @@ -535,7 +547,7 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, l4g.Error("Failed to send mention email successfully email=%v err=%v", profileMap[id].Email, err) } - if len(utils.Cfg.EmailSettings.ApplePushServer) > 0 { + if *utils.Cfg.EmailSettings.SendPushNotifications { sessionChan := Srv.Store.Session().GetSessions(id) if result := <-sessionChan; result.Err != nil { l4g.Error("Failed to retrieve sessions in notifications id=%v, err=%v", id, result.Err) @@ -544,11 +556,27 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, alreadySeen := make(map[string]string) for _, session := range sessions { - if len(session.DeviceId) > 0 && alreadySeen[session.DeviceId] == "" { - + if len(session.DeviceId) > 0 && alreadySeen[session.DeviceId] == "" && strings.HasPrefix(session.DeviceId, "apple:") { alreadySeen[session.DeviceId] = session.DeviceId - utils.SendAppleNotifyAndForget(session.DeviceId, subjectPage.Render(), 1) + msg := model.PushNotification{} + msg.Platform = model.PUSH_NOTIFY_APPLE + msg.Badge = 1 + msg.DeviceId = strings.TrimPrefix(session.DeviceId, "apple:") + msg.ServerId = utils.CfgDiagnosticId + + if channel.Type == model.CHANNEL_DIRECT { + msg.Message = channelName + " sent you a direct message" + } else { + msg.Message = profileMap[id].FirstName + " mentioned you in " + channelName + } + + httpClient := http.Client{} + request, _ := http.NewRequest("POST", *utils.Cfg.EmailSettings.PushNotificationServer+"/api/v1/send_push", strings.NewReader(msg.ToJson())) + + if _, err := httpClient.Do(request); err != nil { + l4g.Error("Failed to send push notificationid=%v, err=%v", id, err) + } } } } @@ -768,6 +796,41 @@ func getPost(c *Context, w http.ResponseWriter, r *http.Request) { } } +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 result := <-Srv.Store.Post().Get(postId); result.Err != nil { + c.Err = result.Err + return + } else { + list := result.Data.(*model.PostList) + + if len(list.Order) != 1 { + c.Err = model.NewAppError("getPostById", "Unable to get post", "") + return + } + post := list.Posts[list.Order[0]] + + cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, post.ChannelId, c.Session.UserId) + if !c.HasPermissionsToChannel(cchan, "getPostById") { + return + } + + if HandleEtag(list.Etag(), w, r) { + return + } + + 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) diff --git a/api/server.go b/api/server.go index 347f0e2c9..2bab62fac 100644 --- a/api/server.go +++ b/api/server.go @@ -9,15 +9,14 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" - "github.com/throttled/throttled" - throttledStore "github.com/throttled/throttled/store" + "gopkg.in/throttled/throttled.v1" + throttledStore "gopkg.in/throttled/throttled.v1/store" "net/http" "strings" "time" ) type Server struct { - Server *manners.GracefulServer Store store.Store Router *mux.Router } @@ -29,7 +28,6 @@ func NewServer() { l4g.Info("Server is initializing...") Srv = &Server{} - Srv.Server = manners.NewServer() Srv.Store = store.NewSqlStore() Srv.Router = mux.NewRouter() @@ -71,7 +69,7 @@ func StartServer() { } go func() { - err := Srv.Server.ListenAndServe(utils.Cfg.ServiceSettings.ListenAddress, handler) + err := manners.ListenAndServe(utils.Cfg.ServiceSettings.ListenAddress, handler) if err != nil { l4g.Critical("Error starting server, err:%v", err) time.Sleep(time.Second) @@ -84,7 +82,7 @@ func StopServer() { l4g.Info("Stopping Server...") - Srv.Server.Shutdown <- true + manners.Close() Srv.Store.Close() hub.Stop() diff --git a/api/team.go b/api/team.go index 862970887..2cc7106dc 100644 --- a/api/team.go +++ b/api/team.go @@ -582,6 +582,39 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(oldTeam.ToJson())) } +func PermanentDeleteTeam(c *Context, team *model.Team) *model.AppError { + l4g.Warn("Attempting to permanently delete team %v id=%v", team.Name, team.Id) + c.Path = "/teams/permanent_delete" + c.LogAuditWithUserId("", fmt.Sprintf("attempt teamId=%v", team.Id)) + + team.DeleteAt = model.GetMillis() + if result := <-Srv.Store.Team().Update(team); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.User().GetForExport(team.Id); result.Err != nil { + return result.Err + } else { + users := result.Data.([]*model.User) + for _, user := range users { + PermanentDeleteUser(c, user) + } + } + + if result := <-Srv.Store.Channel().PermanentDeleteByTeam(team.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Team().PermanentDelete(team.Id); result.Err != nil { + return result.Err + } + + l4g.Warn("Permanently deleted team %v id=%v", team.Name, team.Id) + c.LogAuditWithUserId("", fmt.Sprintf("success teamId=%v", team.Id)) + + return nil +} + func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { if len(c.Session.TeamId) == 0 { diff --git a/api/team_test.go b/api/team_test.go index 7a3b092ce..0b7d2ed9c 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -168,6 +168,45 @@ func TestGetAllTeams(t *testing.T) { } } +func TestTeamPermDelete(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + 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 := PermanentDeleteTeam(c, team) + if err != nil { + t.Fatal(err) + } + + Client.ClearOAuthToken() +} + /* XXXXXX investigate and fix failing test @@ -221,6 +260,7 @@ func TestFindTeamByEmailSend(t *testing.T) { user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) + Client.LoginByEmail(team.Name, user.Email, "pwd") if _, err := Client.FindTeamsSendEmail(user.Email); err != nil { t.Fatal(err) diff --git a/api/templates/signup_team_subject.html b/api/templates/signup_team_subject.html index 236b288fa..4fc5b3d72 100644 --- a/api/templates/signup_team_subject.html +++ b/api/templates/signup_team_subject.html @@ -1 +1 @@ -{{define "signup_team_subject"}}Invitation to {{ .ClientCfg.SiteName }}{{end}}
\ No newline at end of file +{{define "signup_team_subject"}}{{ .ClientCfg.SiteName }} Team Setup{{end}}
\ No newline at end of file diff --git a/api/user.go b/api/user.go index 4a52cf88b..886e38c91 100644 --- a/api/user.go +++ b/api/user.go @@ -334,6 +334,7 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { c.Err = result.Err + c.Err.StatusCode = http.StatusForbidden return nil } else { user := result.Data.(*model.User) @@ -660,7 +661,7 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { profiles := result.Data.(map[string]*model.User) for k, p := range profiles { - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false if c.IsSystemAdmin() { @@ -669,6 +670,7 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { } p.Sanitize(options) + p.ClearNonProfileFields() profiles[k] = p } @@ -1100,7 +1102,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { } } - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) w.Write([]byte(ruser.ToJson())) @@ -1195,6 +1197,14 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { } } + ruser := UpdateActive(c, user, active) + + if c.Err == nil { + w.Write([]byte(ruser.ToJson())) + } +} + +func UpdateActive(c *Context, user *model.User, active bool) *model.User { if active { user.DeleteAt = 0 } else { @@ -1203,7 +1213,7 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { if result := <-Srv.Store.User().Update(user, true); result.Err != nil { c.Err = result.Err - return + return nil } else { c.LogAuditWithUserId(user.Id, fmt.Sprintf("active=%v", active)) @@ -1212,11 +1222,64 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { } ruser := result.Data.([2]*model.User)[0] - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) - w.Write([]byte(ruser.ToJson())) + return ruser + } +} + +func PermanentDeleteUser(c *Context, user *model.User) *model.AppError { + l4g.Warn("Attempting to permanently delete account %v id=%v", user.Email, user.Id) + c.Path = "/users/permanent_delete" + c.LogAuditWithUserId(user.Id, fmt.Sprintf("attempt userId=%v", user.Id)) + c.LogAuditWithUserId("", fmt.Sprintf("attempt userId=%v", user.Id)) + if user.IsInRole(model.ROLE_SYSTEM_ADMIN) { + l4g.Warn("You are deleting %v that is a system administrator. You may need to set another account as the system administrator using the command line tools.", user.Email) + } + + UpdateActive(c, user, false) + + if result := <-Srv.Store.Session().PermanentDeleteSessionsByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.OAuth().PermanentDeleteAuthDataByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Webhook().PermanentDeleteIncomingByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Webhook().PermanentDeleteOutgoingByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Channel().PermanentDeleteMembersByUser(user.Id); result.Err != nil { + return result.Err } + + if result := <-Srv.Store.Post().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.User().PermanentDelete(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Audit().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + l4g.Warn("Permanently deleted account %v id=%v", user.Email, user.Id) + c.LogAuditWithUserId("", fmt.Sprintf("success userId=%v", user.Id)) + + return nil } func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1485,7 +1548,7 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAuditWithUserId(user.Id, "") ruser := result.Data.([2]*model.User)[0] - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) w.Write([]byte(ruser.ToJson())) diff --git a/api/user_test.go b/api/user_test.go index f067182cb..63a1e337b 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -767,6 +767,45 @@ func TestUserUpdateActive(t *testing.T) { } } +func TestUserPermDelete(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + 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 := PermanentDeleteUser(c, user1) + if err != nil { + t.Fatal(err) + } + + Client.ClearOAuthToken() +} + func TestSendPasswordReset(t *testing.T) { Setup() |