Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,11 @@ func GetAdminUser(ctx context.Context) (*User, error) {
return &admin, nil
}

// GetAdminUserCount returns the count of all administrator
func GetAdminUserCount(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Where("is_admin=?", true).Count(User{})
}

func isUserVisibleToViewerCond(viewer *User) builder.Cond {
if viewer != nil && viewer.IsAdmin {
return builder.NewCond()
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ authorization_failed_desc = The authorization failed because we detected an inva
sspi_auth_failed = SSPI authentication failed
password_pwned = The password you chose is on a <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">list of stolen passwords</a> previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too.
password_pwned_err = Could not complete request to HaveIBeenPwned
last_admin = You cannot remove the last admin. There must be at least one admin.

[mail]
view_it_on = View it on %s
Expand Down
14 changes: 14 additions & 0 deletions routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ func EditUser(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/User"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
Expand Down Expand Up @@ -254,6 +256,18 @@ func EditUser(ctx *context.APIContext) {
ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
}
if form.Admin != nil {
// Check whether user is the last admin
if ctx.ContextUser.IsAdmin && !*form.Admin {
num, err := user_model.GetAdminUserCount(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetAdminUserCount", err)
return
}
if num == 1 {
ctx.Error(http.StatusBadRequest, "LastAdmin", ctx.Tr("auth.last_admin"))
return
}
}
ctx.ContextUser.IsAdmin = *form.Admin
}
if form.AllowGitHook != nil {
Expand Down
13 changes: 13 additions & 0 deletions routers/web/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,19 @@ func EditUserPost(ctx *context.Context) {

}

// Check whether user is the last admin
if u.IsAdmin && !form.Admin {
num, err := user_model.GetAdminUserCount(ctx)
if err != nil {
ctx.ServerError("GetAdminUserCount", err)
return
}
if num == 1 {
ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
return
}
}

u.LoginName = form.LoginName
u.FullName = form.FullName
emailChanged := !strings.EqualFold(u.Email, form.Email)
Expand Down
3 changes: 3 additions & 0 deletions templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.