diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 3343af95a9752..ba3be75ab2e19 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -639,6 +639,9 @@ PATH = ;; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false ;ALLOW_ONLY_EXTERNAL_REGISTRATION = false ;; +;; Disable local user management (i.e. when user data and password comes from LDAP and should not be changed locally in gitea). +;DISABLE_LOCAL_USER_MANAGEMENT = false +;; ;; User must sign in to view anything. ;REQUIRE_SIGNIN_VIEW = false ;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index d8a3b897cc019..00bbfa3d8bb41 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -595,6 +595,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o - `ALLOW_ONLY_INTERNAL_REGISTRATION`: **false** Set to true to force registration only via Gitea. - `ALLOW_ONLY_EXTERNAL_REGISTRATION`: **false** Set to true to force registration only using third-party services. - `NO_REPLY_ADDRESS`: **noreply.DOMAIN** Value for the domain part of the user's email address in the Git log if user has set KeepEmailPrivate to true. DOMAIN resolves to the value in server.DOMAIN. +- `DISABLE_LOCAL_USER_MANAGEMENT`: **false** Set to true to disable local user management in gitea (i.e. when users are managed in LDAP). The user's email will be replaced with a concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. - `USER_DELETE_WITH_COMMENTS_MAX_TIME`: **0** Minimum amount of time a user must exist before comments are kept when the user is deleted. - `VALID_SITE_URL_SCHEMES`: **http, https**: Valid site url schemes for user profiles diff --git a/models/user/user.go b/models/user/user.go index 57a7fcadfa96f..d5f17857c1229 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -827,7 +827,43 @@ func validateUser(u *User) error { return ValidateEmail(u.Email) } +// updateUserAllowed is used to block updating selected user fields when local user managemement is disabled. +func updateUserAllowed(u *User) error { + // Don't allow changes of selected user fields if local user management is disabled. + if setting.Service.DisableLocalUserManagement && u.Type == UserTypeIndividual { + if currUser, err := GetUserByID(u.ID); err == nil { + if currUser.Name != u.Name { + return fmt.Errorf("cannot change user %s username; local user management disabled", u.Name) + } + if (currUser.LoginSource != u.LoginSource) || (currUser.LoginName != u.LoginName) { + return fmt.Errorf("cannot change user %s login; local user management disabled", u.Name) + } + if currUser.FullName != u.FullName { + return fmt.Errorf("cannot change user %s full name; local user management disabled", u.Name) + } + if currUser.Email != u.Email { + return fmt.Errorf("cannot change user %s e-mail; local user management disabled", u.Name) + } + if (currUser.Passwd != u.Passwd) || (currUser.PasswdHashAlgo != u.PasswdHashAlgo) { + return fmt.Errorf("cannot change user %s password; local user management disabled", u.Name) + } + if currUser.IsActive != u.IsActive { + return fmt.Errorf("cannot change user %s activity; local user management disabled", u.Name) + } + if currUser.IsAdmin != u.IsAdmin { + return fmt.Errorf("cannot change user %s admin permission; local user management disabled", u.Name) + } + } else { + return err + } + } + return nil +} + func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error { + if err := updateUserAllowed(u); err != nil { + return err + } if err := validateUser(u); err != nil { return err } @@ -873,15 +909,25 @@ func UpdateUser(u *User, emailChanged bool) error { // UpdateUserCols update user according special columns func UpdateUserCols(ctx context.Context, u *User, cols ...string) error { - return updateUserCols(db.GetEngine(ctx), u, cols...) + return updateUserCols(db.GetEngine(ctx), u, false, cols...) +} + +// UpdateForceUserCols force update user according special columns. +func UpdateForceUserCols(ctx context.Context, u *User, cols ...string) error { + return updateUserCols(db.GetEngine(ctx), u, true, cols...) } // UpdateUserColsEngine update user according special columns func UpdateUserColsEngine(e db.Engine, u *User, cols ...string) error { - return updateUserCols(e, u, cols...) + return updateUserCols(e, u, false, cols...) } -func updateUserCols(e db.Engine, u *User, cols ...string) error { +func updateUserCols(e db.Engine, u *User, force bool, cols ...string) error { + if !force { + if err := updateUserAllowed(u); err != nil { + return err + } + } if err := validateUser(u); err != nil { return err } diff --git a/modules/setting/service.go b/modules/setting/service.go index a391926382c4c..5f3d6e3707b7e 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -30,6 +30,7 @@ var Service = struct { DisableRegistration bool AllowOnlyInternalRegistration bool AllowOnlyExternalRegistration bool + DisableLocalUserManagement bool ShowRegistrationButton bool ShowMilestonesDashboardPage bool RequireSignInView bool @@ -115,6 +116,7 @@ func newService() { } else { Service.RegisterManualConfirm = false } + Service.DisableLocalUserManagement = sec.Key("DISABLE_LOCAL_USER_MANAGEMENT").MustBool() Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",") Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",") Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration)) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 255866e2ed1c6..466942e1d8355 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -240,6 +240,9 @@ func NewFuncMap() []template.FuncMap { "DisableImportLocal": func() bool { return !setting.ImportLocalPaths }, + "DisableLocalUserManagement": func() bool { + return setting.Service.DisableLocalUserManagement + }, "Dict": func(values ...interface{}) (map[string]interface{}, error) { if len(values)%2 != 0 { return nil, errors.New("invalid dict call") diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 338a54c5dd264..1d14f75e44296 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -49,6 +49,12 @@ func Authentications(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true + // No access to this page if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("Authentications", fmt.Errorf("access to Authentications function denied; local user management disabled")) + return + } + var err error ctx.Data["Sources"], err = auth.Sources() if err != nil { @@ -111,6 +117,12 @@ func NewAuthSource(ctx *context.Context) { ctx.Data["SSPISeparatorReplacement"] = "_" ctx.Data["SSPIDefaultLanguage"] = "" + // No access to this page if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("NewAuthSource", fmt.Errorf("access to NewAuthSource function denied; local user management disabled")) + return + } + // only the first as default ctx.Data["oauth2_provider"] = oauth2providers[0] @@ -240,6 +252,12 @@ func NewAuthSourcePost(ctx *context.Context) { ctx.Data["SSPISeparatorReplacement"] = "_" ctx.Data["SSPIDefaultLanguage"] = "" + // Don't allow to create auth source if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("NewAuthSourcePost", fmt.Errorf("cannot create auth source; local user management disabled")) + return + } + hasTLS := false var config convert.Conversion switch auth.Type(form.Type) { @@ -314,6 +332,12 @@ func EditAuthSource(ctx *context.Context) { oauth2providers := oauth2.GetOAuth2Providers() ctx.Data["OAuth2Providers"] = oauth2providers + // No access to this page if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("EditAuthSource", fmt.Errorf("access to EditAuthSource page denied; local user management disabled")) + return + } + source, err := auth.GetSourceByID(ctx.ParamsInt64(":authid")) if err != nil { ctx.ServerError("auth.GetSourceByID", err) @@ -349,6 +373,12 @@ func EditAuthSourcePost(ctx *context.Context) { oauth2providers := oauth2.GetOAuth2Providers() ctx.Data["OAuth2Providers"] = oauth2providers + // Don't allow to update auth source if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("EditAuthSourcePost", fmt.Errorf("cannot update auth source; local user management disabled")) + return + } + source, err := auth.GetSourceByID(ctx.ParamsInt64(":authid")) if err != nil { ctx.ServerError("auth.GetSourceByID", err) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index b94f9d72c403d..f8d24db2257eb 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -6,6 +6,7 @@ package admin import ( "bytes" + "fmt" "net/http" "net/url" @@ -28,6 +29,12 @@ func Emails(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminEmails"] = true + // No access to this page if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("Emails", fmt.Errorf("access to Emails function denied; local user management disabled")) + return + } + opts := &user_model.SearchEmailOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, @@ -109,6 +116,12 @@ func isKeywordValid(keyword string) bool { // ActivateEmail serves a POST request for activating/deactivating a user's email func ActivateEmail(ctx *context.Context) { + // Don't allow to activate/deactivate emails if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("ActivateEmail", fmt.Errorf("cannot activate email; local user management disabled")) + return + } + truefalse := map[string]bool{"1": true, "0": false} uid := ctx.FormInt64("uid") diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 5cb25d8672e23..14e1876cf6415 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -6,6 +6,7 @@ package admin import ( + "fmt" "net/http" "net/url" "strconv" @@ -81,6 +82,12 @@ func NewUser(ctx *context.Context) { ctx.Data["login_type"] = "0-0" + // No access to this page if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("NewUser", fmt.Errorf("access to NewUser function denied; local user management disabled")) + return + } + sources, err := auth.Sources() if err != nil { ctx.ServerError("auth.Sources", err) @@ -100,6 +107,11 @@ func NewUserPost(ctx *context.Context) { ctx.Data["PageIsAdminUsers"] = true ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("NewUserPost", fmt.Errorf("cannot create new user; local user management disabled")) + return + } + sources, err := auth.Sources() if err != nil { ctx.ServerError("auth.Sources", err) @@ -278,6 +290,13 @@ func EditUserPost(ctx *context.Context) { if len(form.Password) > 0 && (u.IsLocal() || u.IsOAuth2()) { var err error + + // Don't allow password changes if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("UpdateUser", fmt.Errorf("cannot change %s password; local user management disabled", u.Name)) + return + } + if len(form.Password) < setting.MinPasswordLength { ctx.Data["Err_Password"] = true ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form) diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index b73122fa12a17..c523ef6703488 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -7,6 +7,7 @@ package setting import ( "errors" + "fmt" "net/http" "time" @@ -94,6 +95,11 @@ func EmailPost(ctx *context.Context) { // Make emailaddress primary. if ctx.FormString("_method") == "PRIMARY" { + // No access to this function if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("MakeEmailPrimary", fmt.Errorf("access to MakeEmailPrimary function denied; local user management disabled")) + return + } if err := user_model.MakeEmailPrimary(&user_model.EmailAddress{ID: ctx.FormInt64("id")}); err != nil { ctx.ServerError("MakeEmailPrimary", err) return @@ -105,6 +111,11 @@ func EmailPost(ctx *context.Context) { } // Send activation Email if ctx.FormString("_method") == "SENDACTIVATION" { + // No access to this function if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("SendActivation", fmt.Errorf("access to SendActivation function denied; local user management disabled")) + return + } var address string if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) { log.Error("Send activation: activation still pending") @@ -170,6 +181,12 @@ func EmailPost(ctx *context.Context) { return } + // No access to this function if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("AddEmailAddress", fmt.Errorf("access to AddEmailAddress function denied; local user management disabled")) + return + } + if ctx.HasError() { loadAccountData(ctx) @@ -215,6 +232,12 @@ func EmailPost(ctx *context.Context) { // DeleteEmail response for delete user's email func DeleteEmail(ctx *context.Context) { + // No access to this function if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("DeleteEmail", fmt.Errorf("access to DeleteEmail function denied; local user management disabled")) + return + } + if err := user_model.DeleteEmailAddress(&user_model.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.User.ID}); err != nil { ctx.ServerError("DeleteEmail", err) return @@ -229,6 +252,12 @@ func DeleteEmail(ctx *context.Context) { // DeleteAccount render user suicide page and response for delete user himself func DeleteAccount(ctx *context.Context) { + // No access to this page if local user management is disabled. + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("DeleteAccount", fmt.Errorf("access to DeleteAccount function denied; local user management disabled")) + return + } + ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index e77e02348c6e1..bd3630b9a11db 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -60,6 +60,9 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s // Check if user name has been changed if user.LowerName != strings.ToLower(newName) { + if setting.Service.DisableLocalUserManagement { + ctx.ServerError("ChangeUserName", fmt.Errorf("cannot change user %s username; local user management disabled", user.Name)) + } if err := user_model.ChangeUserName(user, newName); err != nil { switch { case user_model.IsErrUserAlreadyExist(err): diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 52971bb87e58c..754c3d4871c36 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" ) @@ -37,7 +38,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str return nil, err } } - if user != nil && !user.ProhibitLogin { + if user != nil && (!user.ProhibitLogin || setting.Service.DisableLocalUserManagement) { cols := make([]string, 0) if len(source.AdminFilter) > 0 && user.IsAdmin != sr.IsAdmin { // Change existing admin flag only if AdminFilter option is set @@ -49,8 +50,13 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str user.IsRestricted = sr.IsRestricted cols = append(cols, "is_restricted") } + if user.ProhibitLogin && setting.Service.DisableLocalUserManagement { + // When local user management is disabled, active user is allowed to login. + user.ProhibitLogin = false + cols = append(cols, "prohibit_login") + } if len(cols) > 0 { - err = user_model.UpdateUserCols(db.DefaultContext, user, cols...) + err = user_model.UpdateForceUserCols(db.DefaultContext, user, cols...) if err != nil { return nil, err } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 398d9ef79883d..4456ccf992f07 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" user_service "code.gitea.io/gitea/services/user" ) @@ -138,7 +139,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { (len(source.RestrictedFilter) > 0 && usr.IsRestricted != su.IsRestricted) || !strings.EqualFold(usr.Email, su.Mail) || usr.FullName != fullName || - !usr.IsActive { + !usr.IsActive || + usr.ProhibitLogin != (usr.ProhibitLogin && !setting.Service.DisableLocalUserManagement) { log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name) @@ -153,8 +155,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { usr.IsRestricted = su.IsRestricted } usr.IsActive = true + // When local user management is disabled, active user is allowed to login. + usr.ProhibitLogin = usr.ProhibitLogin && !setting.Service.DisableLocalUserManagement - err = user_model.UpdateUserCols(db.DefaultContext, usr, "full_name", "email", "is_admin", "is_restricted", "is_active") + err = user_model.UpdateForceUserCols(db.DefaultContext, usr, "full_name", "email", "is_admin", "is_restricted", "is_active", "prohibit_login") if err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err) } @@ -194,7 +198,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name) usr.IsActive = false - err = user_model.UpdateUserCols(db.DefaultContext, usr, "is_active") + err = user_model.UpdateForceUserCols(db.DefaultContext, usr, "is_active") if err != nil { log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) } diff --git a/services/forms/user_form.go b/services/forms/user_form.go index a886e89f87c4d..2e63765b67ad9 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -54,6 +54,7 @@ type InstallForm struct { EnableOpenIDSignUp bool DisableRegistration bool AllowOnlyExternalRegistration bool + DisableLocalUserManagement bool EnableCaptcha bool RequireSignInView bool DefaultKeepEmailPrivate bool diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index c656d0619b339..34ea1f08cb6bb 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -17,12 +17,14 @@ {{.i18n.Tr "admin.hooks"}} {{end}} + {{if not DisableLocalUserManagement}} {{.i18n.Tr "admin.authentication"}} {{.i18n.Tr "admin.emails"}} + {{end}} {{.i18n.Tr "admin.config"}} diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 17bd2b936c5ee..9bcf668eabd0c 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -15,7 +15,7 @@ -
{{.i18n.Tr "admin.users.password_helper"}}
@@ -95,19 +95,19 @@{{.i18n.Tr "settings.profile_desc"}}