Merge pull request #1448 from mattermost/PLT-975

PLT-975 adding delete to command line tool
Этот коммит содержится в:
Joram Wilander
2015-11-18 12:48:45 -05:00
родитель 9660adb007 c115191d6b
Коммит e408d615c0
25 изменённых файлов: 890 добавлений и 5 удалений

Просмотреть файл

@@ -582,6 +582,39 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(oldTeam.ToJson())) 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) { func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
if len(c.Session.TeamId) == 0 { if len(c.Session.TeamId) == 0 {

Просмотреть файл

@@ -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 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 := &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) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
Client.LoginByEmail(team.Name, user.Email, "pwd")
if _, err := Client.FindTeamsSendEmail(user.Email); err != nil { if _, err := Client.FindTeamsSendEmail(user.Email); err != nil {
t.Fatal(err) t.Fatal(err)

Просмотреть файл

@@ -1196,6 +1196,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 { if active {
user.DeleteAt = 0 user.DeleteAt = 0
} else { } else {
@@ -1204,7 +1212,7 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
if result := <-Srv.Store.User().Update(user, true); result.Err != nil { if result := <-Srv.Store.User().Update(user, true); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return nil
} else { } else {
c.LogAuditWithUserId(user.Id, fmt.Sprintf("active=%v", active)) c.LogAuditWithUserId(user.Id, fmt.Sprintf("active=%v", active))
@@ -1216,10 +1224,63 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
options := utils.SanitizeOptions options := utils.SanitizeOptions
options["passwordupdate"] = false options["passwordupdate"] = false
ruser.Sanitize(options) 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) { func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)

Просмотреть файл

@@ -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) { func TestSendPasswordReset(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -30,6 +30,8 @@ var flagCmdCreateUser bool
var flagCmdAssignRole bool var flagCmdAssignRole bool
var flagCmdVersion bool var flagCmdVersion bool
var flagCmdResetPassword bool var flagCmdResetPassword bool
var flagCmdPermanentDeleteUser bool
var flagCmdPermanentDeleteTeam bool
var flagConfigFile string var flagConfigFile string
var flagEmail string var flagEmail string
var flagPassword string var flagPassword string
@@ -191,10 +193,18 @@ func parseCmds() {
flag.BoolVar(&flagCmdAssignRole, "assign_role", false, "") flag.BoolVar(&flagCmdAssignRole, "assign_role", false, "")
flag.BoolVar(&flagCmdVersion, "version", false, "") flag.BoolVar(&flagCmdVersion, "version", false, "")
flag.BoolVar(&flagCmdResetPassword, "reset_password", false, "") flag.BoolVar(&flagCmdResetPassword, "reset_password", false, "")
flag.BoolVar(&flagCmdPermanentDeleteUser, "permanent_delete_user", false, "")
flag.BoolVar(&flagCmdPermanentDeleteTeam, "permanent_delete_team", false, "")
flag.Parse() flag.Parse()
flagRunCmds = flagCmdCreateTeam || flagCmdCreateUser || flagCmdAssignRole || flagCmdResetPassword || flagCmdVersion flagRunCmds = (flagCmdCreateTeam ||
flagCmdCreateUser ||
flagCmdAssignRole ||
flagCmdResetPassword ||
flagCmdVersion ||
flagCmdPermanentDeleteUser ||
flagCmdPermanentDeleteTeam)
} }
func runCmds() { func runCmds() {
@@ -203,6 +213,8 @@ func runCmds() {
cmdCreateUser() cmdCreateUser()
cmdAssignRole() cmdAssignRole()
cmdResetPassword() cmdResetPassword()
cmdPermDeleteUser()
cmdPermDeleteTeam()
} }
func cmdCreateTeam() { func cmdCreateTeam() {
@@ -406,6 +418,106 @@ func cmdResetPassword() {
} }
} }
func cmdPermDeleteUser() {
if flagCmdPermanentDeleteUser {
if len(flagTeamName) == 0 {
fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name")
flag.Usage()
os.Exit(1)
}
if len(flagEmail) == 0 {
fmt.Fprintln(os.Stderr, "flag needs an argument: -email")
flag.Usage()
os.Exit(1)
}
c := &api.Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
var team *model.Team
if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
team = result.Data.(*model.Team)
}
var user *model.User
if result := <-api.Srv.Store.User().GetByEmail(team.Id, flagEmail); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
user = result.Data.(*model.User)
}
var confirmBackup string
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "YES" {
flushLogAndExit(1)
}
var confirm string
fmt.Printf("Are you sure you want to delete the user %v? All data will be permanently deleted? (YES/NO): ", user.Email)
fmt.Scanln(&confirm)
if confirm != "YES" {
flushLogAndExit(1)
}
if err := api.PermanentDeleteUser(c, user); err != nil {
l4g.Error("%v", err)
flushLogAndExit(1)
} else {
flushLogAndExit(0)
}
}
}
func cmdPermDeleteTeam() {
if flagCmdPermanentDeleteTeam {
if len(flagTeamName) == 0 {
fmt.Fprintln(os.Stderr, "flag needs an argument: -team_name")
flag.Usage()
os.Exit(1)
}
c := &api.Context{}
c.RequestId = model.NewId()
c.IpAddress = "cmd_line"
var team *model.Team
if result := <-api.Srv.Store.Team().GetByName(flagTeamName); result.Err != nil {
l4g.Error("%v", result.Err)
flushLogAndExit(1)
} else {
team = result.Data.(*model.Team)
}
var confirmBackup string
fmt.Print("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirmBackup)
if confirmBackup != "YES" {
flushLogAndExit(1)
}
var confirm string
fmt.Printf("Are you sure you want to delete the team %v? All data will be permanently deleted? (YES/NO): ", team.Name)
fmt.Scanln(&confirm)
if confirm != "YES" {
flushLogAndExit(1)
}
if err := api.PermanentDeleteTeam(c, team); err != nil {
l4g.Error("%v", err)
flushLogAndExit(1)
} else {
flushLogAndExit(0)
}
}
}
func flushLogAndExit(code int) { func flushLogAndExit(code int) {
l4g.Close() l4g.Close()
time.Sleep(time.Second) time.Sleep(time.Second)
@@ -461,5 +573,19 @@ Usage:
Example: Example:
platform -reset_password -team_name="name" -email="user@example.com" -password="newpassword" platform -reset_password -team_name="name" -email="user@example.com" -password="newpassword"
-permanent_delete_user Permanently deletes a user and all related information
include posts from the database. It requires the
-team_name, and -email flag. You may need to restart the
server to invlidate the cache
Example:
platform -permanent_delete_user -team_name="name" -email="user@example.com"
-permanent_delete_team Permanently deletes a team and all users along with
all related information including posts from the database.
It requires the -team_name flag. You may need to restart
the server to invalidate the cache.
Example:
platform -permanent_delete_team -team_name="name"
` `

Просмотреть файл

@@ -86,3 +86,22 @@ func (s SqlAuditStore) Get(user_id string, limit int) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlAuditStore) PermanentDeleteByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM Audits WHERE UserId = :userId",
map[string]interface{}{"userId": userId}); err != nil {
result.Err = model.NewAppError("SqlAuditStore.Delete", "We encountered an error deleting the audits", "user_id="+userId)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -44,4 +44,8 @@ func TestSqlAuditStore(t *testing.T) {
if len(audits) != 0 { if len(audits) != 0 {
t.Fatal("Should have returned empty because user_id is missing") t.Fatal("Should have returned empty because user_id is missing")
} }
if r2 := <-store.Audit().PermanentDeleteByUser(audit.UserId); r2.Err != nil {
t.Fatal(r2.Err)
}
} }

Просмотреть файл

@@ -279,6 +279,23 @@ func (s SqlChannelStore) Delete(channelId string, time int64) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlChannelStore) PermanentDeleteByTeam(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM Channels WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewAppError("SqlChannelStore.PermanentDeleteByTeam", "We couldn't delete the channels", "teamId="+teamId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
type channelWithMember struct { type channelWithMember struct {
model.Channel model.Channel
model.ChannelMember model.ChannelMember
@@ -616,6 +633,23 @@ func (s SqlChannelStore) RemoveMember(channelId string, userId string) StoreChan
return storeChannel return storeChannel
} }
func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE UserId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewAppError("SqlChannelStore.RemoveMember", "We couldn't remove the channel member", "user_id="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlChannelStore) CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel { func (s SqlChannelStore) CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -379,6 +379,63 @@ func TestChannelMemberStore(t *testing.T) {
} }
} }
func TestChannelDeleteMemberStore(t *testing.T) {
Setup()
c1 := model.Channel{}
c1.TeamId = model.NewId()
c1.DisplayName = "NameName"
c1.Name = "a" + model.NewId() + "b"
c1.Type = model.CHANNEL_OPEN
c1 = *Must(store.Channel().Save(&c1)).(*model.Channel)
c1t1 := (<-store.Channel().Get(c1.Id)).Data.(*model.Channel)
t1 := c1t1.ExtraUpdateAt
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
u1.Nickname = model.NewId()
Must(store.User().Save(&u1))
u2 := model.User{}
u2.TeamId = model.NewId()
u2.Email = model.NewId()
u2.Nickname = model.NewId()
Must(store.User().Save(&u2))
o1 := model.ChannelMember{}
o1.ChannelId = c1.Id
o1.UserId = u1.Id
o1.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&o1))
o2 := model.ChannelMember{}
o2.ChannelId = c1.Id
o2.UserId = u2.Id
o2.NotifyProps = model.GetDefaultChannelNotifyProps()
Must(store.Channel().SaveMember(&o2))
c1t2 := (<-store.Channel().Get(c1.Id)).Data.(*model.Channel)
t2 := c1t2.ExtraUpdateAt
if t2 <= t1 {
t.Fatal("Member update time incorrect")
}
count := (<-store.Channel().GetMemberCount(o1.ChannelId)).Data.(int64)
if count != 2 {
t.Fatal("should have saved 2 members")
}
Must(store.Channel().PermanentDeleteMembersByUser(o2.UserId))
count = (<-store.Channel().GetMemberCount(o1.ChannelId)).Data.(int64)
if count != 1 {
t.Fatal("should have removed 1 member")
}
}
func TestChannelStorePermissionsTo(t *testing.T) { func TestChannelStorePermissionsTo(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -332,3 +332,21 @@ func (as SqlOAuthStore) RemoveAuthData(code string) StoreChannel {
return storeChannel return storeChannel
} }
func (as SqlOAuthStore) PermanentDeleteAuthDataByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := as.GetMaster().Exec("DELETE FROM OAuthAuthData WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewAppError("SqlOAuthStore.RemoveAuthDataByUserId", "We couldn't remove the authorization code", "err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -180,3 +180,17 @@ func TestOAuthStoreRemoveAuthData(t *testing.T) {
t.Fatal("should have errored - auth code removed") t.Fatal("should have errored - auth code removed")
} }
} }
func TestOAuthStoreRemoveAuthDataByUser(t *testing.T) {
Setup()
a1 := model.AuthData{}
a1.ClientId = model.NewId()
a1.UserId = model.NewId()
a1.Code = model.NewId()
Must(store.OAuth().SaveAuthData(&a1))
if err := (<-store.OAuth().PermanentDeleteAuthDataByUser(a1.UserId)).Err; err != nil {
t.Fatal(err)
}
}

Просмотреть файл

@@ -228,6 +228,99 @@ func (s SqlPostStore) Delete(postId string, time int64) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlPostStore) permanentDelete(postId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE Id = :Id OR ParentId = :ParentId OR RootId = :RootId", map[string]interface{}{"Id": postId, "ParentId": postId, "RootId": postId})
if err != nil {
result.Err = model.NewAppError("SqlPostStore.Delete", "We couldn't delete the post", "id="+postId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) permanentDeleteAllCommentByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM Posts WHERE UserId = :UserId AND RootId != ''", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewAppError("SqlPostStore.permanentDeleteAllCommentByUser", "We couldn't delete the comments for user", "userId="+userId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) PermanentDeleteByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
// First attempt to delete all the comments for a user
if r := <-s.permanentDeleteAllCommentByUser(userId); r.Err != nil {
result.Err = r.Err
storeChannel <- result
close(storeChannel)
return
}
// Now attempt to delete all the root posts for a user. This will also
// delete all the comments for each post.
found := true
count := 0
for found {
var ids []string
_, err := s.GetMaster().Select(&ids, "SELECT Id FROM Posts WHERE UserId = :UserId LIMIT 1000", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.select", "We couldn't select the posts to delete for the user", "userId="+userId+", err="+err.Error())
storeChannel <- result
close(storeChannel)
return
} else {
found = false
for _, id := range ids {
found = true
if r := <-s.permanentDelete(id); r.Err != nil {
result.Err = r.Err
storeChannel <- result
close(storeChannel)
return
}
}
}
// This is a fail safe, give up if more than 10K messages
count = count + 1
if count >= 10 {
result.Err = model.NewAppError("SqlPostStore.PermanentDeleteByUser.toolarge", "We couldn't select the posts to delete for the user (too many), please re-run", "userId="+userId)
storeChannel <- result
close(storeChannel)
return
}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) GetPosts(channelId string, offset int, limit int) StoreChannel { func (s SqlPostStore) GetPosts(channelId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -247,6 +247,76 @@ func TestPostStoreDelete2Level(t *testing.T) {
} }
} }
func TestPostStorePermDelete1Level(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
if r2 := <-store.Post().PermanentDeleteByUser(o2.UserId); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Post().Get(o1.Id)); r3.Err != nil {
t.Fatal("Deleted id shouldn't have failed")
}
if r4 := (<-store.Post().Get(o2.Id)); r4.Err == nil {
t.Fatal("Deleted id should have failed")
}
}
func TestPostStorePermDelete1Level2(t *testing.T) {
Setup()
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
o3 := &model.Post{}
o3.ChannelId = model.NewId()
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "b"
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
if r2 := <-store.Post().PermanentDeleteByUser(o1.UserId); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Post().Get(o1.Id)); r3.Err == nil {
t.Fatal("Deleted id should have failed")
}
if r4 := (<-store.Post().Get(o2.Id)); r4.Err == nil {
t.Fatal("Deleted id should have failed")
}
if r5 := (<-store.Post().Get(o3.Id)); r5.Err != nil {
t.Fatal("Deleted id shouldn't have failed")
}
}
func TestPostStoreGetWithChildren(t *testing.T) { func TestPostStoreGetWithChildren(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -239,3 +239,21 @@ func (s SqlPreferenceStore) GetAll(userId string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlPreferenceStore) PermanentDeleteByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec(
`DELETE FROM Preferences WHERE UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewAppError("SqlPreferenceStore.Delete", "We encountered an error while deleteing preferences", err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -192,3 +192,43 @@ func TestPreferenceGetAll(t *testing.T) {
} }
} }
} }
func TestPreferenceDelete(t *testing.T) {
Setup()
userId := model.NewId()
category := model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
name := model.NewId()
preferences := model.Preferences{
{
UserId: userId,
Category: category,
Name: name,
},
// same user/category, different name
{
UserId: userId,
Category: category,
Name: model.NewId(),
},
// same user/name, different category
{
UserId: userId,
Category: model.NewId(),
Name: name,
},
// same name/category, different user
{
UserId: model.NewId(),
Category: category,
Name: name,
},
}
Must(store.Preference().Save(&preferences))
if result := <-store.Preference().PermanentDeleteByUser(userId); result.Err != nil {
t.Fatal(result.Err)
}
}

Просмотреть файл

@@ -158,6 +158,24 @@ func (me SqlSessionStore) RemoveAllSessionsForTeam(teamId string) StoreChannel {
return storeChannel return storeChannel
} }
func (me SqlSessionStore) PermanentDeleteSessionsByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewAppError("SqlSessionStore.RemoveAllSessionsForUser", "We couldn't remove all the sessions for the user", "id="+userId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (me SqlSessionStore) CleanUpExpiredSessions(userId string) StoreChannel { func (me SqlSessionStore) CleanUpExpiredSessions(userId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -103,6 +103,29 @@ func TestSessionRemoveAll(t *testing.T) {
} }
} }
func TestSessionRemoveByUser(t *testing.T) {
Setup()
s1 := model.Session{}
s1.UserId = model.NewId()
s1.TeamId = model.NewId()
Must(store.Session().Save(&s1))
if rs1 := (<-store.Session().Get(s1.Id)); rs1.Err != nil {
t.Fatal(rs1.Err)
} else {
if rs1.Data.(*model.Session).Id != s1.Id {
t.Fatal("should match")
}
}
Must(store.Session().PermanentDeleteSessionsByUser(s1.UserId))
if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil {
t.Fatal("should have been removed")
}
}
func TestSessionRemoveToken(t *testing.T) { func TestSessionRemoveToken(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -300,3 +300,20 @@ func (s SqlTeamStore) GetAllTeamListing() StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlTeamStore) PermanentDelete(teamId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec("DELETE FROM Teams WHERE Id = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil {
result.Err = model.NewAppError("SqlTeamStore.Delete", "We couldn't delete the existing team", "teamId="+teamId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -238,3 +238,26 @@ func TestAllTeamListing(t *testing.T) {
} }
} }
} }
func TestDelete(t *testing.T) {
Setup()
o1 := model.Team{}
o1.DisplayName = "DisplayName"
o1.Name = "a" + model.NewId() + "b"
o1.Email = model.NewId() + "@nowhere.com"
o1.Type = model.TEAM_OPEN
o1.AllowTeamListing = true
Must(store.Team().Save(&o1))
o2 := model.Team{}
o2.DisplayName = "DisplayName"
o2.Name = "a" + model.NewId() + "b"
o2.Email = model.NewId() + "@nowhere.com"
o2.Type = model.TEAM_OPEN
Must(store.Team().Save(&o2))
if r1 := <-store.Team().PermanentDelete(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
}
}

Просмотреть файл

@@ -554,3 +554,21 @@ func (us SqlUserStore) GetTotalActiveUsersCount() StoreChannel {
return storeChannel return storeChannel
} }
func (us SqlUserStore) PermanentDelete(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := us.GetMaster().Exec("DELETE FROM Users WHERE Id = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetByEmail", "We couldn't delete the existing account", "userId="+userId+", "+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

Просмотреть файл

@@ -377,3 +377,16 @@ func TestUserStoreUpdatePassword(t *testing.T) {
} }
} }
} }
func TestUserStoreDelete(t *testing.T) {
Setup()
u1 := model.User{}
u1.TeamId = model.NewId()
u1.Email = model.NewId()
Must(store.User().Save(&u1))
if err := (<-store.User().PermanentDelete(u1.Id)).Err; err != nil {
t.Fatal(err)
}
}

Просмотреть файл

@@ -116,6 +116,24 @@ func (s SqlWebhookStore) DeleteIncoming(webhookId string, time int64) StoreChann
return storeChannel return storeChannel
} }
func (s SqlWebhookStore) PermanentDeleteIncomingByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM IncomingWebhooks WHERE UserId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewAppError("SqlWebhookStore.DeleteIncomingByUser", "We couldn't delete the webhook", "id="+userId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel { func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
@@ -294,6 +312,24 @@ func (s SqlWebhookStore) DeleteOutgoing(webhookId string, time int64) StoreChann
return storeChannel return storeChannel
} }
func (s SqlWebhookStore) PermanentDeleteOutgoingByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := s.GetMaster().Exec("DELETE FROM OutgoingWebhooks WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId})
if err != nil {
result.Err = model.NewAppError("SqlWebhookStore.DeleteOutgoingByUser", "We couldn't delete the webhook", "id="+userId+", err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlWebhookStore) UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel { func (s SqlWebhookStore) UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -103,6 +103,34 @@ func TestWebhookStoreDeleteIncoming(t *testing.T) {
} }
} }
func TestWebhookStoreDeleteIncomingByUser(t *testing.T) {
Setup()
o1 := &model.IncomingWebhook{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.TeamId = model.NewId()
o1 = (<-store.Webhook().SaveIncoming(o1)).Data.(*model.IncomingWebhook)
if r1 := <-store.Webhook().GetIncoming(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.IncomingWebhook).CreateAt != o1.CreateAt {
t.Fatal("invalid returned webhook")
}
}
if r2 := <-store.Webhook().PermanentDeleteIncomingByUser(o1.UserId); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Webhook().GetIncoming(o1.Id)); r3.Err == nil {
t.Log(r3.Data)
t.Fatal("Missing id should have failed")
}
}
func TestWebhookStoreSaveOutgoing(t *testing.T) { func TestWebhookStoreSaveOutgoing(t *testing.T) {
Setup() Setup()
@@ -258,6 +286,35 @@ func TestWebhookStoreDeleteOutgoing(t *testing.T) {
} }
} }
func TestWebhookStoreDeleteOutgoingByUser(t *testing.T) {
Setup()
o1 := &model.OutgoingWebhook{}
o1.ChannelId = model.NewId()
o1.CreatorId = model.NewId()
o1.TeamId = model.NewId()
o1.CallbackURLs = []string{"http://nowhere.com/"}
o1 = (<-store.Webhook().SaveOutgoing(o1)).Data.(*model.OutgoingWebhook)
if r1 := <-store.Webhook().GetOutgoing(o1.Id); r1.Err != nil {
t.Fatal(r1.Err)
} else {
if r1.Data.(*model.OutgoingWebhook).CreateAt != o1.CreateAt {
t.Fatal("invalid returned webhook")
}
}
if r2 := <-store.Webhook().PermanentDeleteOutgoingByUser(o1.CreatorId); r2.Err != nil {
t.Fatal(r2.Err)
}
if r3 := (<-store.Webhook().GetOutgoing(o1.Id)); r3.Err == nil {
t.Log(r3.Data)
t.Fatal("Missing id should have failed")
}
}
func TestWebhookStoreUpdateOutgoing(t *testing.T) { func TestWebhookStoreUpdateOutgoing(t *testing.T) {
Setup() Setup()

Просмотреть файл

@@ -52,6 +52,7 @@ type TeamStore interface {
GetAll() StoreChannel GetAll() StoreChannel
GetAllTeamListing() StoreChannel GetAllTeamListing() StoreChannel
GetByInviteId(inviteId string) StoreChannel GetByInviteId(inviteId string) StoreChannel
PermanentDelete(teamId string) StoreChannel
} }
type ChannelStore interface { type ChannelStore interface {
@@ -60,6 +61,7 @@ type ChannelStore interface {
Update(channel *model.Channel) StoreChannel Update(channel *model.Channel) StoreChannel
Get(id string) StoreChannel Get(id string) StoreChannel
Delete(channelId string, time int64) StoreChannel Delete(channelId string, time int64) StoreChannel
PermanentDeleteByTeam(teamId string) StoreChannel
GetByName(team_id string, domain string) StoreChannel GetByName(team_id string, domain string) StoreChannel
GetChannels(teamId string, userId string) StoreChannel GetChannels(teamId string, userId string) StoreChannel
GetMoreChannels(teamId string, userId string) StoreChannel GetMoreChannels(teamId string, userId string) StoreChannel
@@ -72,6 +74,7 @@ type ChannelStore interface {
GetMember(channelId string, userId string) StoreChannel GetMember(channelId string, userId string) StoreChannel
GetMemberCount(channelId string) StoreChannel GetMemberCount(channelId string) StoreChannel
RemoveMember(channelId string, userId string) StoreChannel RemoveMember(channelId string, userId string) StoreChannel
PermanentDeleteMembersByUser(userId string) StoreChannel
GetExtraMembers(channelId string, limit int) StoreChannel GetExtraMembers(channelId string, limit int) StoreChannel
CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel CheckPermissionsTo(teamId string, channelId string, userId string) StoreChannel
CheckOpenChannelPermissions(teamId string, channelId string) StoreChannel CheckOpenChannelPermissions(teamId string, channelId string) StoreChannel
@@ -86,6 +89,7 @@ type PostStore interface {
Update(post *model.Post, newMessage string, newHashtags string) StoreChannel Update(post *model.Post, newMessage string, newHashtags string) StoreChannel
Get(id string) StoreChannel Get(id string) StoreChannel
Delete(postId string, time int64) StoreChannel Delete(postId string, time int64) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel
GetPosts(channelId string, offset int, limit int) StoreChannel GetPosts(channelId string, offset int, limit int) StoreChannel
GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel
GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel
@@ -118,6 +122,7 @@ type UserStore interface {
GetTotalUsersCount() StoreChannel GetTotalUsersCount() StoreChannel
GetTotalActiveUsersCount() StoreChannel GetTotalActiveUsersCount() StoreChannel
GetSystemAdminProfiles() StoreChannel GetSystemAdminProfiles() StoreChannel
PermanentDelete(userId string) StoreChannel
} }
type SessionStore interface { type SessionStore interface {
@@ -126,6 +131,7 @@ type SessionStore interface {
GetSessions(userId string) StoreChannel GetSessions(userId string) StoreChannel
Remove(sessionIdOrToken string) StoreChannel Remove(sessionIdOrToken string) StoreChannel
RemoveAllSessionsForTeam(teamId string) StoreChannel RemoveAllSessionsForTeam(teamId string) StoreChannel
PermanentDeleteSessionsByUser(teamId string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel UpdateLastActivityAt(sessionId string, time int64) StoreChannel
UpdateRoles(userId string, roles string) StoreChannel UpdateRoles(userId string, roles string) StoreChannel
} }
@@ -133,6 +139,7 @@ type SessionStore interface {
type AuditStore interface { type AuditStore interface {
Save(audit *model.Audit) StoreChannel Save(audit *model.Audit) StoreChannel
Get(user_id string, limit int) StoreChannel Get(user_id string, limit int) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel
} }
type OAuthStore interface { type OAuthStore interface {
@@ -143,6 +150,7 @@ type OAuthStore interface {
SaveAuthData(authData *model.AuthData) StoreChannel SaveAuthData(authData *model.AuthData) StoreChannel
GetAuthData(code string) StoreChannel GetAuthData(code string) StoreChannel
RemoveAuthData(code string) StoreChannel RemoveAuthData(code string) StoreChannel
PermanentDeleteAuthDataByUser(userId string) StoreChannel
SaveAccessData(accessData *model.AccessData) StoreChannel SaveAccessData(accessData *model.AccessData) StoreChannel
GetAccessData(token string) StoreChannel GetAccessData(token string) StoreChannel
GetAccessDataByAuthCode(authCode string) StoreChannel GetAccessDataByAuthCode(authCode string) StoreChannel
@@ -161,12 +169,14 @@ type WebhookStore interface {
GetIncomingByUser(userId string) StoreChannel GetIncomingByUser(userId string) StoreChannel
GetIncomingByChannel(channelId string) StoreChannel GetIncomingByChannel(channelId string) StoreChannel
DeleteIncoming(webhookId string, time int64) StoreChannel DeleteIncoming(webhookId string, time int64) StoreChannel
PermanentDeleteIncomingByUser(userId string) StoreChannel
SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel
GetOutgoing(id string) StoreChannel GetOutgoing(id string) StoreChannel
GetOutgoingByCreator(userId string) StoreChannel GetOutgoingByCreator(userId string) StoreChannel
GetOutgoingByChannel(channelId string) StoreChannel GetOutgoingByChannel(channelId string) StoreChannel
GetOutgoingByTeam(teamId string) StoreChannel GetOutgoingByTeam(teamId string) StoreChannel
DeleteOutgoing(webhookId string, time int64) StoreChannel DeleteOutgoing(webhookId string, time int64) StoreChannel
PermanentDeleteOutgoingByUser(userId string) StoreChannel
UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel UpdateOutgoing(hook *model.OutgoingWebhook) StoreChannel
} }
@@ -175,4 +185,5 @@ type PreferenceStore interface {
Get(userId string, category string, name string) StoreChannel Get(userId string, category string, name string) StoreChannel
GetCategory(userId string, category string) StoreChannel GetCategory(userId string, category string) StoreChannel
GetAll(userId string) StoreChannel GetAll(userId string) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel
} }

Просмотреть файл

@@ -59,8 +59,7 @@ func FindDir(dir string) string {
func ConfigureCmdLineLog() { func ConfigureCmdLineLog() {
ls := model.LogSettings{} ls := model.LogSettings{}
ls.EnableConsole = true ls.EnableConsole = true
ls.ConsoleLevel = "ERROR" ls.ConsoleLevel = "WARN"
ls.EnableFile = false
configureLog(&ls) configureLog(&ls)
} }
@@ -72,6 +71,8 @@ func configureLog(s *model.LogSettings) {
level := l4g.DEBUG level := l4g.DEBUG
if s.ConsoleLevel == "INFO" { if s.ConsoleLevel == "INFO" {
level = l4g.INFO level = l4g.INFO
} else if s.ConsoleLevel == "WARN" {
level = l4g.WARNING
} else if s.ConsoleLevel == "ERROR" { } else if s.ConsoleLevel == "ERROR" {
level = l4g.ERROR level = l4g.ERROR
} }
@@ -90,6 +91,8 @@ func configureLog(s *model.LogSettings) {
level := l4g.DEBUG level := l4g.DEBUG
if s.FileLevel == "INFO" { if s.FileLevel == "INFO" {
level = l4g.INFO level = l4g.INFO
} else if s.FileLevel == "WARN" {
level = l4g.WARNING
} else if s.FileLevel == "ERROR" { } else if s.FileLevel == "ERROR" {
level = l4g.ERROR level = l4g.ERROR
} }