code.gitea.io/gitea@v1.22.3/routers/web/admin/users.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2020 The Gitea Authors.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package admin
     6  
     7  import (
     8  	"errors"
     9  	"net/http"
    10  	"net/url"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/models"
    15  	"code.gitea.io/gitea/models/auth"
    16  	"code.gitea.io/gitea/models/db"
    17  	org_model "code.gitea.io/gitea/models/organization"
    18  	repo_model "code.gitea.io/gitea/models/repo"
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	"code.gitea.io/gitea/modules/auth/password"
    21  	"code.gitea.io/gitea/modules/base"
    22  	"code.gitea.io/gitea/modules/log"
    23  	"code.gitea.io/gitea/modules/optional"
    24  	"code.gitea.io/gitea/modules/setting"
    25  	"code.gitea.io/gitea/modules/util"
    26  	"code.gitea.io/gitea/modules/web"
    27  	"code.gitea.io/gitea/routers/web/explore"
    28  	user_setting "code.gitea.io/gitea/routers/web/user/setting"
    29  	"code.gitea.io/gitea/services/context"
    30  	"code.gitea.io/gitea/services/forms"
    31  	"code.gitea.io/gitea/services/mailer"
    32  	user_service "code.gitea.io/gitea/services/user"
    33  )
    34  
    35  const (
    36  	tplUsers    base.TplName = "admin/user/list"
    37  	tplUserNew  base.TplName = "admin/user/new"
    38  	tplUserView base.TplName = "admin/user/view"
    39  	tplUserEdit base.TplName = "admin/user/edit"
    40  )
    41  
    42  // UserSearchDefaultAdminSort is the default sort type for admin view
    43  const UserSearchDefaultAdminSort = "alphabetically"
    44  
    45  // Users show all the users
    46  func Users(ctx *context.Context) {
    47  	ctx.Data["Title"] = ctx.Tr("admin.users")
    48  	ctx.Data["PageIsAdminUsers"] = true
    49  
    50  	extraParamStrings := map[string]string{}
    51  	statusFilterKeys := []string{"is_active", "is_admin", "is_restricted", "is_2fa_enabled", "is_prohibit_login"}
    52  	statusFilterMap := map[string]string{}
    53  	for _, filterKey := range statusFilterKeys {
    54  		paramKey := "status_filter[" + filterKey + "]"
    55  		paramVal := ctx.FormString(paramKey)
    56  		statusFilterMap[filterKey] = paramVal
    57  		if paramVal != "" {
    58  			extraParamStrings[paramKey] = paramVal
    59  		}
    60  	}
    61  
    62  	sortType := ctx.FormString("sort")
    63  	if sortType == "" {
    64  		sortType = UserSearchDefaultAdminSort
    65  		ctx.SetFormString("sort", sortType)
    66  	}
    67  	ctx.PageData["adminUserListSearchForm"] = map[string]any{
    68  		"StatusFilterMap": statusFilterMap,
    69  		"SortType":        sortType,
    70  	}
    71  
    72  	explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
    73  		Actor: ctx.Doer,
    74  		Type:  user_model.UserTypeIndividual,
    75  		ListOptions: db.ListOptions{
    76  			PageSize: setting.UI.Admin.UserPagingNum,
    77  		},
    78  		SearchByEmail:      true,
    79  		IsActive:           util.OptionalBoolParse(statusFilterMap["is_active"]),
    80  		IsAdmin:            util.OptionalBoolParse(statusFilterMap["is_admin"]),
    81  		IsRestricted:       util.OptionalBoolParse(statusFilterMap["is_restricted"]),
    82  		IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]),
    83  		IsProhibitLogin:    util.OptionalBoolParse(statusFilterMap["is_prohibit_login"]),
    84  		IncludeReserved:    true, // administrator needs to list all accounts include reserved, bot, remote ones
    85  		ExtraParamStrings:  extraParamStrings,
    86  	}, tplUsers)
    87  }
    88  
    89  // NewUser render adding a new user page
    90  func NewUser(ctx *context.Context) {
    91  	ctx.Data["Title"] = ctx.Tr("admin.users.new_account")
    92  	ctx.Data["PageIsAdminUsers"] = true
    93  	ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode
    94  	ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
    95  
    96  	ctx.Data["login_type"] = "0-0"
    97  
    98  	sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
    99  		IsActive: optional.Some(true),
   100  	})
   101  	if err != nil {
   102  		ctx.ServerError("auth.Sources", err)
   103  		return
   104  	}
   105  	ctx.Data["Sources"] = sources
   106  
   107  	ctx.Data["CanSendEmail"] = setting.MailService != nil
   108  	ctx.HTML(http.StatusOK, tplUserNew)
   109  }
   110  
   111  // NewUserPost response for adding a new user
   112  func NewUserPost(ctx *context.Context) {
   113  	form := web.GetForm(ctx).(*forms.AdminCreateUserForm)
   114  	ctx.Data["Title"] = ctx.Tr("admin.users.new_account")
   115  	ctx.Data["PageIsAdminUsers"] = true
   116  	ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode
   117  	ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
   118  
   119  	sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
   120  		IsActive: optional.Some(true),
   121  	})
   122  	if err != nil {
   123  		ctx.ServerError("auth.Sources", err)
   124  		return
   125  	}
   126  	ctx.Data["Sources"] = sources
   127  
   128  	ctx.Data["CanSendEmail"] = setting.MailService != nil
   129  
   130  	if ctx.HasError() {
   131  		ctx.HTML(http.StatusOK, tplUserNew)
   132  		return
   133  	}
   134  
   135  	u := &user_model.User{
   136  		Name:      form.UserName,
   137  		Email:     form.Email,
   138  		Passwd:    form.Password,
   139  		LoginType: auth.Plain,
   140  	}
   141  
   142  	overwriteDefault := &user_model.CreateUserOverwriteOptions{
   143  		IsActive:   optional.Some(true),
   144  		Visibility: &form.Visibility,
   145  	}
   146  
   147  	if len(form.LoginType) > 0 {
   148  		fields := strings.Split(form.LoginType, "-")
   149  		if len(fields) == 2 {
   150  			lType, _ := strconv.ParseInt(fields[0], 10, 0)
   151  			u.LoginType = auth.Type(lType)
   152  			u.LoginSource, _ = strconv.ParseInt(fields[1], 10, 64)
   153  			u.LoginName = form.LoginName
   154  		}
   155  	}
   156  	if u.LoginType == auth.NoType || u.LoginType == auth.Plain {
   157  		if len(form.Password) < setting.MinPasswordLength {
   158  			ctx.Data["Err_Password"] = true
   159  			ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserNew, &form)
   160  			return
   161  		}
   162  		if !password.IsComplexEnough(form.Password) {
   163  			ctx.Data["Err_Password"] = true
   164  			ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserNew, &form)
   165  			return
   166  		}
   167  		if err := password.IsPwned(ctx, form.Password); err != nil {
   168  			ctx.Data["Err_Password"] = true
   169  			errMsg := ctx.Tr("auth.password_pwned")
   170  			if password.IsErrIsPwnedRequest(err) {
   171  				log.Error(err.Error())
   172  				errMsg = ctx.Tr("auth.password_pwned_err")
   173  			}
   174  			ctx.RenderWithErr(errMsg, tplUserNew, &form)
   175  			return
   176  		}
   177  		u.MustChangePassword = form.MustChangePassword
   178  	}
   179  
   180  	if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
   181  		switch {
   182  		case user_model.IsErrUserAlreadyExist(err):
   183  			ctx.Data["Err_UserName"] = true
   184  			ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserNew, &form)
   185  		case user_model.IsErrEmailAlreadyUsed(err):
   186  			ctx.Data["Err_Email"] = true
   187  			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
   188  		case user_model.IsErrEmailInvalid(err), user_model.IsErrEmailCharIsNotSupported(err):
   189  			ctx.Data["Err_Email"] = true
   190  			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
   191  		case db.IsErrNameReserved(err):
   192  			ctx.Data["Err_UserName"] = true
   193  			ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tplUserNew, &form)
   194  		case db.IsErrNamePatternNotAllowed(err):
   195  			ctx.Data["Err_UserName"] = true
   196  			ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplUserNew, &form)
   197  		case db.IsErrNameCharsNotAllowed(err):
   198  			ctx.Data["Err_UserName"] = true
   199  			ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tplUserNew, &form)
   200  		default:
   201  			ctx.ServerError("CreateUser", err)
   202  		}
   203  		return
   204  	}
   205  
   206  	if !user_model.IsEmailDomainAllowed(u.Email) {
   207  		ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", u.Email))
   208  	}
   209  
   210  	log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
   211  
   212  	// Send email notification.
   213  	if form.SendNotify {
   214  		mailer.SendRegisterNotifyMail(u)
   215  	}
   216  
   217  	ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name))
   218  	ctx.Redirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10))
   219  }
   220  
   221  func prepareUserInfo(ctx *context.Context) *user_model.User {
   222  	u, err := user_model.GetUserByID(ctx, ctx.ParamsInt64(":userid"))
   223  	if err != nil {
   224  		if user_model.IsErrUserNotExist(err) {
   225  			ctx.Redirect(setting.AppSubURL + "/admin/users")
   226  		} else {
   227  			ctx.ServerError("GetUserByID", err)
   228  		}
   229  		return nil
   230  	}
   231  	ctx.Data["User"] = u
   232  
   233  	if u.LoginSource > 0 {
   234  		ctx.Data["LoginSource"], err = auth.GetSourceByID(ctx, u.LoginSource)
   235  		if err != nil {
   236  			ctx.ServerError("auth.GetSourceByID", err)
   237  			return nil
   238  		}
   239  	} else {
   240  		ctx.Data["LoginSource"] = &auth.Source{}
   241  	}
   242  
   243  	sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{})
   244  	if err != nil {
   245  		ctx.ServerError("auth.Sources", err)
   246  		return nil
   247  	}
   248  	ctx.Data["Sources"] = sources
   249  
   250  	hasTOTP, err := auth.HasTwoFactorByUID(ctx, u.ID)
   251  	if err != nil {
   252  		ctx.ServerError("auth.HasTwoFactorByUID", err)
   253  		return nil
   254  	}
   255  	hasWebAuthn, err := auth.HasWebAuthnRegistrationsByUID(ctx, u.ID)
   256  	if err != nil {
   257  		ctx.ServerError("auth.HasWebAuthnRegistrationsByUID", err)
   258  		return nil
   259  	}
   260  	ctx.Data["TwoFactorEnabled"] = hasTOTP || hasWebAuthn
   261  
   262  	return u
   263  }
   264  
   265  func ViewUser(ctx *context.Context) {
   266  	ctx.Data["Title"] = ctx.Tr("admin.users.details")
   267  	ctx.Data["PageIsAdminUsers"] = true
   268  	ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
   269  	ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
   270  	ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
   271  
   272  	u := prepareUserInfo(ctx)
   273  	if ctx.Written() {
   274  		return
   275  	}
   276  
   277  	repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
   278  		ListOptions: db.ListOptionsAll,
   279  		OwnerID:     u.ID,
   280  		OrderBy:     db.SearchOrderByAlphabetically,
   281  		Private:     true,
   282  		Collaborate: optional.Some(false),
   283  	})
   284  	if err != nil {
   285  		ctx.ServerError("SearchRepository", err)
   286  		return
   287  	}
   288  
   289  	ctx.Data["Repos"] = repos
   290  	ctx.Data["ReposTotal"] = int(count)
   291  
   292  	emails, err := user_model.GetEmailAddresses(ctx, u.ID)
   293  	if err != nil {
   294  		ctx.ServerError("GetEmailAddresses", err)
   295  		return
   296  	}
   297  	ctx.Data["Emails"] = emails
   298  	ctx.Data["EmailsTotal"] = len(emails)
   299  
   300  	orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
   301  		ListOptions:    db.ListOptionsAll,
   302  		UserID:         u.ID,
   303  		IncludePrivate: true,
   304  	})
   305  	if err != nil {
   306  		ctx.ServerError("FindOrgs", err)
   307  		return
   308  	}
   309  
   310  	ctx.Data["Users"] = orgs // needed to be able to use explore/user_list template
   311  	ctx.Data["OrgsTotal"] = len(orgs)
   312  
   313  	ctx.HTML(http.StatusOK, tplUserView)
   314  }
   315  
   316  func editUserCommon(ctx *context.Context) {
   317  	ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")
   318  	ctx.Data["PageIsAdminUsers"] = true
   319  	ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
   320  	ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
   321  	ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
   322  	ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
   323  }
   324  
   325  // EditUser show editing user page
   326  func EditUser(ctx *context.Context) {
   327  	editUserCommon(ctx)
   328  	prepareUserInfo(ctx)
   329  	if ctx.Written() {
   330  		return
   331  	}
   332  
   333  	ctx.HTML(http.StatusOK, tplUserEdit)
   334  }
   335  
   336  // EditUserPost response for editing user
   337  func EditUserPost(ctx *context.Context) {
   338  	editUserCommon(ctx)
   339  	u := prepareUserInfo(ctx)
   340  	if ctx.Written() {
   341  		return
   342  	}
   343  
   344  	form := web.GetForm(ctx).(*forms.AdminEditUserForm)
   345  	if ctx.HasError() {
   346  		ctx.HTML(http.StatusOK, tplUserEdit)
   347  		return
   348  	}
   349  
   350  	if form.UserName != "" {
   351  		if err := user_service.RenameUser(ctx, u, form.UserName); err != nil {
   352  			switch {
   353  			case user_model.IsErrUserIsNotLocal(err):
   354  				ctx.Data["Err_UserName"] = true
   355  				ctx.RenderWithErr(ctx.Tr("form.username_change_not_local_user"), tplUserEdit, &form)
   356  			case user_model.IsErrUserAlreadyExist(err):
   357  				ctx.Data["Err_UserName"] = true
   358  				ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserEdit, &form)
   359  			case db.IsErrNameReserved(err):
   360  				ctx.Data["Err_UserName"] = true
   361  				ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", form.UserName), tplUserEdit, &form)
   362  			case db.IsErrNamePatternNotAllowed(err):
   363  				ctx.Data["Err_UserName"] = true
   364  				ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", form.UserName), tplUserEdit, &form)
   365  			case db.IsErrNameCharsNotAllowed(err):
   366  				ctx.Data["Err_UserName"] = true
   367  				ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", form.UserName), tplUserEdit, &form)
   368  			default:
   369  				ctx.ServerError("RenameUser", err)
   370  			}
   371  			return
   372  		}
   373  	}
   374  
   375  	authOpts := &user_service.UpdateAuthOptions{
   376  		Password:  optional.FromNonDefault(form.Password),
   377  		LoginName: optional.Some(form.LoginName),
   378  	}
   379  
   380  	// skip self Prohibit Login
   381  	if ctx.Doer.ID == u.ID {
   382  		authOpts.ProhibitLogin = optional.Some(false)
   383  	} else {
   384  		authOpts.ProhibitLogin = optional.Some(form.ProhibitLogin)
   385  	}
   386  
   387  	fields := strings.Split(form.LoginType, "-")
   388  	if len(fields) == 2 {
   389  		authSource, _ := strconv.ParseInt(fields[1], 10, 64)
   390  
   391  		authOpts.LoginSource = optional.Some(authSource)
   392  	}
   393  
   394  	if err := user_service.UpdateAuth(ctx, u, authOpts); err != nil {
   395  		switch {
   396  		case errors.Is(err, password.ErrMinLength):
   397  			ctx.Data["Err_Password"] = true
   398  			ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form)
   399  		case errors.Is(err, password.ErrComplexity):
   400  			ctx.Data["Err_Password"] = true
   401  			ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserEdit, &form)
   402  		case errors.Is(err, password.ErrIsPwned):
   403  			ctx.Data["Err_Password"] = true
   404  			ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplUserEdit, &form)
   405  		case password.IsErrIsPwnedRequest(err):
   406  			ctx.Data["Err_Password"] = true
   407  			ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form)
   408  		default:
   409  			ctx.ServerError("UpdateUser", err)
   410  		}
   411  		return
   412  	}
   413  
   414  	if form.Email != "" {
   415  		if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
   416  			switch {
   417  			case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
   418  				ctx.Data["Err_Email"] = true
   419  				ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
   420  			case user_model.IsErrEmailAlreadyUsed(err):
   421  				ctx.Data["Err_Email"] = true
   422  				ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
   423  			default:
   424  				ctx.ServerError("AddOrSetPrimaryEmailAddress", err)
   425  			}
   426  			return
   427  		}
   428  		if !user_model.IsEmailDomainAllowed(form.Email) {
   429  			ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", form.Email))
   430  		}
   431  	}
   432  
   433  	opts := &user_service.UpdateOptions{
   434  		FullName:                optional.Some(form.FullName),
   435  		Website:                 optional.Some(form.Website),
   436  		Location:                optional.Some(form.Location),
   437  		IsActive:                optional.Some(form.Active),
   438  		IsAdmin:                 optional.Some(form.Admin),
   439  		AllowGitHook:            optional.Some(form.AllowGitHook),
   440  		AllowImportLocal:        optional.Some(form.AllowImportLocal),
   441  		MaxRepoCreation:         optional.Some(form.MaxRepoCreation),
   442  		AllowCreateOrganization: optional.Some(form.AllowCreateOrganization),
   443  		IsRestricted:            optional.Some(form.Restricted),
   444  		Visibility:              optional.Some(form.Visibility),
   445  		Language:                optional.Some(form.Language),
   446  	}
   447  
   448  	if err := user_service.UpdateUser(ctx, u, opts); err != nil {
   449  		if models.IsErrDeleteLastAdminUser(err) {
   450  			ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
   451  		} else {
   452  			ctx.ServerError("UpdateUser", err)
   453  		}
   454  		return
   455  	}
   456  	log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name)
   457  
   458  	if form.Reset2FA {
   459  		tf, err := auth.GetTwoFactorByUID(ctx, u.ID)
   460  		if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
   461  			ctx.ServerError("auth.GetTwoFactorByUID", err)
   462  			return
   463  		} else if tf != nil {
   464  			if err := auth.DeleteTwoFactorByID(ctx, tf.ID, u.ID); err != nil {
   465  				ctx.ServerError("auth.DeleteTwoFactorByID", err)
   466  				return
   467  			}
   468  		}
   469  
   470  		wn, err := auth.GetWebAuthnCredentialsByUID(ctx, u.ID)
   471  		if err != nil {
   472  			ctx.ServerError("auth.GetTwoFactorByUID", err)
   473  			return
   474  		}
   475  		for _, cred := range wn {
   476  			if _, err := auth.DeleteCredential(ctx, cred.ID, u.ID); err != nil {
   477  				ctx.ServerError("auth.DeleteCredential", err)
   478  				return
   479  			}
   480  		}
   481  	}
   482  
   483  	ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success"))
   484  	ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
   485  }
   486  
   487  // DeleteUser response for deleting a user
   488  func DeleteUser(ctx *context.Context) {
   489  	u, err := user_model.GetUserByID(ctx, ctx.ParamsInt64(":userid"))
   490  	if err != nil {
   491  		ctx.ServerError("GetUserByID", err)
   492  		return
   493  	}
   494  
   495  	// admin should not delete themself
   496  	if u.ID == ctx.Doer.ID {
   497  		ctx.Flash.Error(ctx.Tr("admin.users.cannot_delete_self"))
   498  		ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
   499  		return
   500  	}
   501  
   502  	if err = user_service.DeleteUser(ctx, u, ctx.FormBool("purge")); err != nil {
   503  		switch {
   504  		case models.IsErrUserOwnRepos(err):
   505  			ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo"))
   506  			ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
   507  		case models.IsErrUserHasOrgs(err):
   508  			ctx.Flash.Error(ctx.Tr("admin.users.still_has_org"))
   509  			ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
   510  		case models.IsErrUserOwnPackages(err):
   511  			ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
   512  			ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
   513  		case models.IsErrDeleteLastAdminUser(err):
   514  			ctx.Flash.Error(ctx.Tr("auth.last_admin"))
   515  			ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
   516  		default:
   517  			ctx.ServerError("DeleteUser", err)
   518  		}
   519  		return
   520  	}
   521  	log.Trace("Account deleted by admin (%s): %s", ctx.Doer.Name, u.Name)
   522  
   523  	ctx.Flash.Success(ctx.Tr("admin.users.deletion_success"))
   524  	ctx.Redirect(setting.AppSubURL + "/admin/users")
   525  }
   526  
   527  // AvatarPost response for change user's avatar request
   528  func AvatarPost(ctx *context.Context) {
   529  	u := prepareUserInfo(ctx)
   530  	if ctx.Written() {
   531  		return
   532  	}
   533  
   534  	form := web.GetForm(ctx).(*forms.AvatarForm)
   535  	if err := user_setting.UpdateAvatarSetting(ctx, form, u); err != nil {
   536  		ctx.Flash.Error(err.Error())
   537  	} else {
   538  		ctx.Flash.Success(ctx.Tr("settings.update_user_avatar_success"))
   539  	}
   540  
   541  	ctx.Redirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10))
   542  }
   543  
   544  // DeleteAvatar render delete avatar page
   545  func DeleteAvatar(ctx *context.Context) {
   546  	u := prepareUserInfo(ctx)
   547  	if ctx.Written() {
   548  		return
   549  	}
   550  
   551  	if err := user_service.DeleteAvatar(ctx, u); err != nil {
   552  		ctx.Flash.Error(err.Error())
   553  	}
   554  
   555  	ctx.JSONRedirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10))
   556  }