// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present 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 . 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 getProjectsToDelete(s *xorm.Session, u *user.User) (projectsToDelete []*Project, err error) { projectsToDelete = []*Project{} lm := &Project{IsArchived: true} res, _, _, err := lm.ReadAll(s, u, "", 0, -1) if err != nil { return nil, err } if res == nil { return nil, nil } projects := res.([]*Project) for _, l := range projects { if l.ID < 0 { continue } hadUsers, err := ensureProjectAdminUser(s, l) if err != nil { return nil, err } if hadUsers { continue } hadTeams, err := ensureProjectAdminTeam(s, l) if err != nil { return nil, err } if hadTeams { continue } projectsToDelete = append(projectsToDelete, l) } return } // DeleteUser completely removes a user and all their associated projects and tasks. // This action is irrevocable. // Public to allow deletion from the CLI. func DeleteUser(s *xorm.Session, u *user.User) (err error) { projectsToDelete, err := getProjectsToDelete(s, u) if err != nil { return err } for _, p := range projectsToDelete { if p.ParentProjectID != 0 { // Child projects are deleted by p.Delete continue } err = p.Delete(s, u) // If the user is the owner of the default project it will be deleted, if they are not the owner // we can ignore the error as the project was shared in that case. if err != nil && !IsErrCannotDeleteDefaultProject(err) { return err } } _, err = s.Where("id = ?", u.ID).Delete(&user.User{}) if err != nil { return err } return notifications.Notify(u, &user.AccountDeletedNotification{ User: u, }) } func ensureProjectAdminUser(s *xorm.Session, l *Project) (hadUsers bool, err error) { projectUsers := []*ProjectUser{} err = s.Where("project_id = ?", l.ID).Find(&projectUsers) if err != nil { return } if len(projectUsers) == 0 { return false, nil } for _, lu := range projectUsers { if lu.Right == RightAdmin { // Project already has more than one admin, no need to do anything return true, nil } } firstUser := projectUsers[0] firstUser.Right = RightAdmin _, err = s.Where("id = ?", firstUser.ID). Cols("right"). Update(firstUser) return true, err } func ensureProjectAdminTeam(s *xorm.Session, l *Project) (hadTeams bool, err error) { projectTeams := []*TeamProject{} err = s.Where("project_id = ?", l.ID).Find(&projectTeams) if err != nil { return } if len(projectTeams) == 0 { return false, nil } for _, lu := range projectTeams { if lu.Right == RightAdmin { // Project already has more than one admin, no need to do anything return true, nil } } firstTeam := projectTeams[0] firstTeam.Right = RightAdmin _, err = s.Where("id = ?", firstTeam.ID). Cols("right"). Update(firstTeam) return true, err }