code.gitea.io/gitea@v1.21.7/services/user/user.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package user
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"code.gitea.io/gitea/models"
    14  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    15  	"code.gitea.io/gitea/models/db"
    16  	"code.gitea.io/gitea/models/organization"
    17  	packages_model "code.gitea.io/gitea/models/packages"
    18  	repo_model "code.gitea.io/gitea/models/repo"
    19  	system_model "code.gitea.io/gitea/models/system"
    20  	user_model "code.gitea.io/gitea/models/user"
    21  	"code.gitea.io/gitea/modules/eventsource"
    22  	"code.gitea.io/gitea/modules/log"
    23  	"code.gitea.io/gitea/modules/setting"
    24  	"code.gitea.io/gitea/modules/storage"
    25  	"code.gitea.io/gitea/modules/util"
    26  	"code.gitea.io/gitea/services/agit"
    27  	org_service "code.gitea.io/gitea/services/org"
    28  	"code.gitea.io/gitea/services/packages"
    29  	container_service "code.gitea.io/gitea/services/packages/container"
    30  	repo_service "code.gitea.io/gitea/services/repository"
    31  )
    32  
    33  // RenameUser renames a user
    34  func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error {
    35  	// Non-local users are not allowed to change their username.
    36  	if !u.IsOrganization() && !u.IsLocal() {
    37  		return user_model.ErrUserIsNotLocal{
    38  			UID:  u.ID,
    39  			Name: u.Name,
    40  		}
    41  	}
    42  
    43  	if newUserName == u.Name {
    44  		return user_model.ErrUsernameNotChanged{
    45  			UID:  u.ID,
    46  			Name: u.Name,
    47  		}
    48  	}
    49  
    50  	if err := user_model.IsUsableUsername(newUserName); err != nil {
    51  		return err
    52  	}
    53  
    54  	onlyCapitalization := strings.EqualFold(newUserName, u.Name)
    55  	oldUserName := u.Name
    56  
    57  	if onlyCapitalization {
    58  		u.Name = newUserName
    59  		if err := user_model.UpdateUserCols(ctx, u, "name"); err != nil {
    60  			u.Name = oldUserName
    61  			return err
    62  		}
    63  		return repo_model.UpdateRepositoryOwnerNames(ctx, u.ID, newUserName)
    64  	}
    65  
    66  	ctx, committer, err := db.TxContext(ctx)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	defer committer.Close()
    71  
    72  	isExist, err := user_model.IsUserExist(ctx, u.ID, newUserName)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	if isExist {
    77  		return user_model.ErrUserAlreadyExist{
    78  			Name: newUserName,
    79  		}
    80  	}
    81  
    82  	if err = repo_model.UpdateRepositoryOwnerName(ctx, oldUserName, newUserName); err != nil {
    83  		return err
    84  	}
    85  
    86  	if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
    87  		return err
    88  	}
    89  
    90  	if err := agit.UserNameChanged(ctx, u, newUserName); err != nil {
    91  		return err
    92  	}
    93  	if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil {
    94  		return err
    95  	}
    96  
    97  	u.Name = newUserName
    98  	u.LowerName = strings.ToLower(newUserName)
    99  	if err := user_model.UpdateUserCols(ctx, u, "name", "lower_name"); err != nil {
   100  		u.Name = oldUserName
   101  		u.LowerName = strings.ToLower(oldUserName)
   102  		return err
   103  	}
   104  
   105  	// Do not fail if directory does not exist
   106  	if err = util.Rename(user_model.UserPath(oldUserName), user_model.UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
   107  		u.Name = oldUserName
   108  		u.LowerName = strings.ToLower(oldUserName)
   109  		return fmt.Errorf("rename user directory: %w", err)
   110  	}
   111  
   112  	if err = committer.Commit(); err != nil {
   113  		u.Name = oldUserName
   114  		u.LowerName = strings.ToLower(oldUserName)
   115  		if err2 := util.Rename(user_model.UserPath(newUserName), user_model.UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
   116  			log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
   117  			return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
   118  		}
   119  		return err
   120  	}
   121  	return nil
   122  }
   123  
   124  // DeleteUser completely and permanently deletes everything of a user,
   125  // but issues/comments/pulls will be kept and shown as someone has been deleted,
   126  // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS.
   127  func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
   128  	if u.IsOrganization() {
   129  		return fmt.Errorf("%s is an organization not a user", u.Name)
   130  	}
   131  
   132  	if user_model.IsLastAdminUser(ctx, u) {
   133  		return models.ErrDeleteLastAdminUser{UID: u.ID}
   134  	}
   135  
   136  	if purge {
   137  		// Disable the user first
   138  		// NOTE: This is deliberately not within a transaction as it must disable the user immediately to prevent any further action by the user to be purged.
   139  		if err := user_model.UpdateUserCols(ctx, &user_model.User{
   140  			ID:              u.ID,
   141  			IsActive:        false,
   142  			IsRestricted:    true,
   143  			IsAdmin:         false,
   144  			ProhibitLogin:   true,
   145  			Passwd:          "",
   146  			Salt:            "",
   147  			PasswdHashAlgo:  "",
   148  			MaxRepoCreation: 0,
   149  		}, "is_active", "is_restricted", "is_admin", "prohibit_login", "max_repo_creation", "passwd", "salt", "passwd_hash_algo"); err != nil {
   150  			return fmt.Errorf("unable to disable user: %s[%d] prior to purge. UpdateUserCols: %w", u.Name, u.ID, err)
   151  		}
   152  
   153  		// Force any logged in sessions to log out
   154  		// FIXME: We also need to tell the session manager to log them out too.
   155  		eventsource.GetManager().SendMessage(u.ID, &eventsource.Event{
   156  			Name: "logout",
   157  		})
   158  
   159  		// Delete all repos belonging to this user
   160  		// Now this is not within a transaction because there are internal transactions within the DeleteRepository
   161  		// BUT: the db will still be consistent even if a number of repos have already been deleted.
   162  		// And in fact we want to capture any repositories that are being created in other transactions in the meantime
   163  		//
   164  		// An alternative option here would be write a DeleteAllRepositoriesForUserID function which would delete all of the repos
   165  		// but such a function would likely get out of date
   166  		err := repo_service.DeleteOwnerRepositoriesDirectly(ctx, u)
   167  		if err != nil {
   168  			return err
   169  		}
   170  
   171  		// Remove from Organizations and delete last owner organizations
   172  		// Now this is not within a transaction because there are internal transactions within the DeleteOrganization
   173  		// BUT: the db will still be consistent even if a number of organizations memberships and organizations have already been deleted
   174  		// And in fact we want to capture any organization additions that are being created in other transactions in the meantime
   175  		//
   176  		// An alternative option here would be write a function which would delete all organizations but it seems
   177  		// but such a function would likely get out of date
   178  		for {
   179  			orgs, err := organization.FindOrgs(organization.FindOrgOptions{
   180  				ListOptions: db.ListOptions{
   181  					PageSize: repo_model.RepositoryListDefaultPageSize,
   182  					Page:     1,
   183  				},
   184  				UserID:         u.ID,
   185  				IncludePrivate: true,
   186  			})
   187  			if err != nil {
   188  				return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err)
   189  			}
   190  			if len(orgs) == 0 {
   191  				break
   192  			}
   193  			for _, org := range orgs {
   194  				if err := models.RemoveOrgUser(org.ID, u.ID); err != nil {
   195  					if organization.IsErrLastOrgOwner(err) {
   196  						err = org_service.DeleteOrganization(ctx, org, true)
   197  						if err != nil {
   198  							return fmt.Errorf("unable to delete organization %d: %w", org.ID, err)
   199  						}
   200  					}
   201  					if err != nil {
   202  						return fmt.Errorf("unable to remove user %s[%d] from org %s[%d]. Error: %w", u.Name, u.ID, org.Name, org.ID, err)
   203  					}
   204  				}
   205  			}
   206  		}
   207  
   208  		// Delete Packages
   209  		if setting.Packages.Enabled {
   210  			if _, err := packages.RemoveAllPackages(ctx, u.ID); err != nil {
   211  				return err
   212  			}
   213  		}
   214  	}
   215  
   216  	ctx, committer, err := db.TxContext(db.DefaultContext)
   217  	if err != nil {
   218  		return err
   219  	}
   220  	defer committer.Close()
   221  
   222  	// Note: A user owns any repository or belongs to any organization
   223  	//	cannot perform delete operation. This causes a race with the purge above
   224  	//  however consistency requires that we ensure that this is the case
   225  
   226  	// Check ownership of repository.
   227  	count, err := repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{OwnerID: u.ID})
   228  	if err != nil {
   229  		return fmt.Errorf("GetRepositoryCount: %w", err)
   230  	} else if count > 0 {
   231  		return models.ErrUserOwnRepos{UID: u.ID}
   232  	}
   233  
   234  	// Check membership of organization.
   235  	count, err = organization.GetOrganizationCount(ctx, u)
   236  	if err != nil {
   237  		return fmt.Errorf("GetOrganizationCount: %w", err)
   238  	} else if count > 0 {
   239  		return models.ErrUserHasOrgs{UID: u.ID}
   240  	}
   241  
   242  	// Check ownership of packages.
   243  	if ownsPackages, err := packages_model.HasOwnerPackages(ctx, u.ID); err != nil {
   244  		return fmt.Errorf("HasOwnerPackages: %w", err)
   245  	} else if ownsPackages {
   246  		return models.ErrUserOwnPackages{UID: u.ID}
   247  	}
   248  
   249  	if err := deleteUser(ctx, u, purge); err != nil {
   250  		return fmt.Errorf("DeleteUser: %w", err)
   251  	}
   252  
   253  	if err := committer.Commit(); err != nil {
   254  		return err
   255  	}
   256  	committer.Close()
   257  
   258  	if err = asymkey_model.RewriteAllPublicKeys(ctx); err != nil {
   259  		return err
   260  	}
   261  	if err = asymkey_model.RewriteAllPrincipalKeys(ctx); err != nil {
   262  		return err
   263  	}
   264  
   265  	// Note: There are something just cannot be roll back,
   266  	//	so just keep error logs of those operations.
   267  	path := user_model.UserPath(u.Name)
   268  	if err := util.RemoveAll(path); err != nil {
   269  		err = fmt.Errorf("Failed to RemoveAll %s: %w", path, err)
   270  		_ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
   271  		return err
   272  	}
   273  
   274  	if u.Avatar != "" {
   275  		avatarPath := u.CustomAvatarRelativePath()
   276  		if err := storage.Avatars.Delete(avatarPath); err != nil {
   277  			err = fmt.Errorf("Failed to remove %s: %w", avatarPath, err)
   278  			_ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
   279  			return err
   280  		}
   281  	}
   282  
   283  	return nil
   284  }
   285  
   286  // DeleteInactiveUsers deletes all inactive users and email addresses.
   287  func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
   288  	users, err := user_model.GetInactiveUsers(ctx, olderThan)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	// FIXME: should only update authorized_keys file once after all deletions.
   294  	for _, u := range users {
   295  		select {
   296  		case <-ctx.Done():
   297  			return db.ErrCancelledf("Before delete inactive user %s", u.Name)
   298  		default:
   299  		}
   300  		if err := DeleteUser(ctx, u, false); err != nil {
   301  			// Ignore users that were set inactive by admin.
   302  			if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) ||
   303  				models.IsErrUserOwnPackages(err) || models.IsErrDeleteLastAdminUser(err) {
   304  				continue
   305  			}
   306  			return err
   307  		}
   308  	}
   309  
   310  	return user_model.DeleteInactiveEmailAddresses(ctx)
   311  }