72d3c54efd
Resolves #1024
310 lines
6.9 KiB
Go
310 lines
6.9 KiB
Go
// Vikunja is a to-do list application to facilitate your life.
|
|
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public Licensee as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public Licensee for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public Licensee
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"code.vikunja.io/api/pkg/cron"
|
|
"code.vikunja.io/api/pkg/db"
|
|
"code.vikunja.io/api/pkg/log"
|
|
"code.vikunja.io/api/pkg/notifications"
|
|
"code.vikunja.io/api/pkg/user"
|
|
|
|
"xorm.io/builder"
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
// User deletion must happen here in this packaage because we want to delete everything associated to this user.
|
|
// Because most of these things are managed in the models package, using them has to happen here.
|
|
|
|
// RegisterUserDeletionCron registers the cron job that actually removes users who are scheduled to delete.
|
|
func RegisterUserDeletionCron() {
|
|
err := cron.Schedule("0 * * * *", deleteUsers)
|
|
if err != nil {
|
|
log.Errorf("Could not register deletion cron: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func deleteUsers() {
|
|
s := db.NewSession()
|
|
users := []*user.User{}
|
|
err := s.Where(builder.Lt{"deletion_scheduled_at": time.Now()}).
|
|
Find(&users)
|
|
if err != nil {
|
|
log.Errorf("Could not get users scheduled for deletion: %s", err)
|
|
return
|
|
}
|
|
|
|
if len(users) == 0 {
|
|
return
|
|
}
|
|
|
|
log.Debugf("Found %d users scheduled for deletion", len(users))
|
|
|
|
now := time.Now()
|
|
|
|
for _, u := range users {
|
|
if !u.DeletionScheduledAt.Before(now) {
|
|
log.Debugf("User %d is not yet scheduled for deletion. Scheduled at %s, now is %s", u.ID, u.DeletionScheduledAt, now)
|
|
continue
|
|
}
|
|
|
|
err = s.Begin()
|
|
if err != nil {
|
|
log.Errorf("Could not start transaction: %s", err)
|
|
return
|
|
}
|
|
|
|
err = DeleteUser(s, u)
|
|
if err != nil {
|
|
_ = s.Rollback()
|
|
log.Errorf("Could not delete u %d: %s", u.ID, err)
|
|
return
|
|
}
|
|
|
|
log.Debugf("Deleted user %d", u.ID)
|
|
|
|
err = s.Commit()
|
|
if err != nil {
|
|
log.Errorf("Could not commit transaction: %s", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func getNamespacesToDelete(s *xorm.Session, u *user.User) (namespacesToDelete []*Namespace, err error) {
|
|
namespacesToDelete = []*Namespace{}
|
|
nm := &Namespace{IsArchived: true}
|
|
res, _, _, err := nm.ReadAll(s, u, "", 1, -1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if res == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
namespaces := res.([]*NamespaceWithLists)
|
|
for _, n := range namespaces {
|
|
if n.ID < 0 {
|
|
continue
|
|
}
|
|
|
|
hadUsers, err := ensureNamespaceAdminUser(s, &n.Namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hadUsers {
|
|
continue
|
|
}
|
|
hadTeams, err := ensureNamespaceAdminTeam(s, &n.Namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hadTeams {
|
|
continue
|
|
}
|
|
|
|
namespacesToDelete = append(namespacesToDelete, &n.Namespace)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func getListsToDelete(s *xorm.Session, u *user.User) (listsToDelete []*List, err error) {
|
|
listsToDelete = []*List{}
|
|
lm := &List{IsArchived: true}
|
|
res, _, _, err := lm.ReadAll(s, u, "", 0, -1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if res == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
lists := res.([]*List)
|
|
for _, l := range lists {
|
|
if l.ID < 0 {
|
|
continue
|
|
}
|
|
|
|
hadUsers, err := ensureListAdminUser(s, l)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hadUsers {
|
|
continue
|
|
}
|
|
hadTeams, err := ensureListAdminTeam(s, l)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if hadTeams {
|
|
continue
|
|
}
|
|
|
|
listsToDelete = append(listsToDelete, l)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// DeleteUser completely removes a user and all their associated lists, namespaces and tasks.
|
|
// This action is irrevocable.
|
|
// Public to allow deletion from the CLI.
|
|
func DeleteUser(s *xorm.Session, u *user.User) (err error) {
|
|
namespacesToDelete, err := getNamespacesToDelete(s, u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
listsToDelete, err := getListsToDelete(s, u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Delete everything not shared with anybody else
|
|
for _, n := range namespacesToDelete {
|
|
err = deleteNamespace(s, n, u, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, l := range listsToDelete {
|
|
err = l.Delete(s, u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, err = s.Where("id = ?", u.ID).Delete(u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return notifications.Notify(u, &user.AccountDeletedNotification{
|
|
User: u,
|
|
})
|
|
}
|
|
|
|
func ensureNamespaceAdminUser(s *xorm.Session, n *Namespace) (hadUsers bool, err error) {
|
|
namespaceUsers := []*NamespaceUser{}
|
|
err = s.Where("namespace_id = ?", n.ID).Find(&namespaceUsers)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(namespaceUsers) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
for _, lu := range namespaceUsers {
|
|
if lu.Right == RightAdmin {
|
|
// List already has more than one admin, no need to do anything
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
firstUser := namespaceUsers[0]
|
|
firstUser.Right = RightAdmin
|
|
_, err = s.Where("id = ?", firstUser.ID).
|
|
Cols("right").
|
|
Update(firstUser)
|
|
return true, err
|
|
}
|
|
|
|
func ensureNamespaceAdminTeam(s *xorm.Session, n *Namespace) (hadTeams bool, err error) {
|
|
namespaceTeams := []*TeamNamespace{}
|
|
err = s.Where("namespace_id = ?", n.ID).Find(&namespaceTeams)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(namespaceTeams) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
for _, lu := range namespaceTeams {
|
|
if lu.Right == RightAdmin {
|
|
// List already has more than one admin, no need to do anything
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
firstTeam := namespaceTeams[0]
|
|
firstTeam.Right = RightAdmin
|
|
_, err = s.Where("id = ?", firstTeam.ID).
|
|
Cols("right").
|
|
Update(firstTeam)
|
|
return true, err
|
|
}
|
|
|
|
func ensureListAdminUser(s *xorm.Session, l *List) (hadUsers bool, err error) {
|
|
listUsers := []*ListUser{}
|
|
err = s.Where("list_id = ?", l.ID).Find(&listUsers)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(listUsers) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
for _, lu := range listUsers {
|
|
if lu.Right == RightAdmin {
|
|
// List already has more than one admin, no need to do anything
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
firstUser := listUsers[0]
|
|
firstUser.Right = RightAdmin
|
|
_, err = s.Where("id = ?", firstUser.ID).
|
|
Cols("right").
|
|
Update(firstUser)
|
|
return true, err
|
|
}
|
|
|
|
func ensureListAdminTeam(s *xorm.Session, l *List) (hadTeams bool, err error) {
|
|
listTeams := []*TeamList{}
|
|
err = s.Where("list_id = ?", l.ID).Find(&listTeams)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(listTeams) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
for _, lu := range listTeams {
|
|
if lu.Right == RightAdmin {
|
|
// List already has more than one admin, no need to do anything
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
firstTeam := listTeams[0]
|
|
firstTeam.Right = RightAdmin
|
|
_, err = s.Where("id = ?", firstTeam.ID).
|
|
Cols("right").
|
|
Update(firstTeam)
|
|
return true, err
|
|
}
|