From 795b75ee6934556149da632c7fb496dc3e42efcd Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Tue, 6 Oct 2020 20:13:52 +0200
Subject: [PATCH 01/11] Parameter DISABLE_LOCAL_USER_MANAGEMENT added

Added parameter DISABLE_LOCAL_USER_MANAGEMENT (false by default) in
app.ini [service] section; when true disables local modifications
of username, fullname and e-mail fields in user Settings.

Author-Change-Id: IB#1105051
---
 custom/conf/app.example.ini                    |  2 ++
 .../doc/advanced/config-cheat-sheet.en-us.md   |  1 +
 models/user.go                                 | 18 ++++++++++++++++++
 modules/auth/user_form.go                      |  1 +
 modules/setting/service.go                     |  2 ++
 routers/user/setting/profile.go                |  4 +++-
 templates/user/settings/profile.tmpl           | 10 +++++-----
 7 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index b3b9fd96cc708..385bf6ccb02e8 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -569,6 +569,8 @@ EMAIL_DOMAIN_WHITELIST=
 DISABLE_REGISTRATION = false
 ; 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
 ; Mail notification
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 673eeac79ddae..784e15f3f84ba 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -409,6 +409,7 @@ set name for unique queues. Individual queues will default to
 - `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
 - `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.
 - `ALLOW_ONLY_EXTERNAL_REGISTRATION`: **false** Set to true to force registration only using third-party services.
+- `DISABLE_LOCAL_USER_MANAGEMENT`: **false** Set to true to disable local user management in gitea (i.e. when users are managed in LDAP).
 - `NO_REPLY_ADDRESS`: **DOMAIN** Default value for the domain part of the user's email address in the git log if he has set KeepEmailPrivate to true. 
   The user's email will be replaced with a concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
 
diff --git a/models/user.go b/models/user.go
index 650d5a803aa9a..3688087782127 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1148,6 +1148,24 @@ func updateUserCols(e Engine, u *User, cols ...string) error {
 
 // UpdateUserSetting updates user's settings.
 func UpdateUserSetting(u *User) error {
+
+	// Don't allow username, fullname nor email changes if local user management is disabled.
+	if setting.Service.DisableLocalUserManagement {
+		if currUser, err := GetUserByID(u.ID); err == nil {
+			if currUser.Name != u.Name {
+				return fmt.Errorf("cannot change %s username; local user management disabled", u.Name)
+			}
+			if currUser.FullName != u.FullName {
+				return fmt.Errorf("cannot change %s full name; local user management disabled", u.Name)
+			}
+			if currUser.Email != u.Email {
+				return fmt.Errorf("cannot change %s e-mail; local user management disabled", u.Name)
+			}
+		} else {
+			return err
+		}
+	}
+
 	if !u.IsOrganization() {
 		if err := checkDupEmail(x, u); err != nil {
 			return err
diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go
index 999d4cd74df69..fe9b7bfbf511d 100644
--- a/modules/auth/user_form.go
+++ b/modules/auth/user_form.go
@@ -51,6 +51,7 @@ type InstallForm struct {
 	EnableOpenIDSignUp             bool
 	DisableRegistration            bool
 	AllowOnlyExternalRegistration  bool
+	DisableLocalUserManagement     bool
 	EnableCaptcha                  bool
 	RequireSignInView              bool
 	DefaultKeepEmailPrivate        bool
diff --git a/modules/setting/service.go b/modules/setting/service.go
index c463b0a9d5d0d..5dd33ff1e4a77 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -20,6 +20,7 @@ var Service struct {
 	EmailDomainWhitelist                    []string
 	DisableRegistration                     bool
 	AllowOnlyExternalRegistration           bool
+	DisableLocalUserManagement              bool
 	ShowRegistrationButton                  bool
 	ShowMilestonesDashboardPage             bool
 	RequireSignInView                       bool
@@ -61,6 +62,7 @@ func newService() {
 	Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
 	Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()
 	Service.AllowOnlyExternalRegistration = sec.Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").MustBool()
+	Service.DisableLocalUserManagement = sec.Key("DISABLE_LOCAL_USER_MANAGEMENT").MustBool()
 	Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
 	Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
 	Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go
index fe0506946ad68..98f852d87a3ab 100644
--- a/routers/user/setting/profile.go
+++ b/routers/user/setting/profile.go
@@ -34,13 +34,14 @@ const (
 func Profile(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsProfile"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	ctx.HTML(200, tplSettingsProfile)
 }
 
 func handleUsernameChange(ctx *context.Context, newName string) {
 	// Non-local users are not allowed to change their username.
-	if len(newName) == 0 || !ctx.User.IsLocal() {
+	if len(newName) == 0 || !ctx.User.IsLocal() || setting.Service.DisableLocalUserManagement {
 		return
 	}
 
@@ -80,6 +81,7 @@ func handleUsernameChange(ctx *context.Context, newName string) {
 func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsProfile"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	if ctx.HasError() {
 		ctx.HTML(200, tplSettingsProfile)
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 1f34e24585d6d..dd55de94695c0 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -10,20 +10,20 @@
 			<p>{{.i18n.Tr "settings.profile_desc"}}</p>
 			<form class="ui form" action="{{.Link}}" method="post">
 				{{.CsrfTokenHtml}}
-				<div class="required field {{if .Err_Name}}error{{end}}">
+				<div class="{{if not .DisableLocalUserManagement}}required{{end}} field {{if .Err_Name}}error{{end}}">
 					<label for="username">{{.i18n.Tr "username"}}<span class="text red hide" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span></label>
-					<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if not .SignedUser.IsLocal}}disabled{{end}}>
+					<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus {{if not .DisableLocalUserManagement}}required{{end}} {{if or (not .SignedUser.IsLocal) (.DisableLocalUserManagement) }}disabled{{end}}>
 					{{if not .SignedUser.IsLocal}}
 					<p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p>
 					{{end}}
 				</div>
 				<div class="field {{if .Err_FullName}}error{{end}}">
 					<label for="full_name">{{.i18n.Tr "settings.full_name"}}</label>
-					<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}">
+					<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" {{if .DisableLocalUserManagement}}readonly{{end}}>
 				</div>
-				<div class="required field {{if .Err_Email}}error{{end}}">
+				<div class="{{if not .DisableLocalUserManagement}}required{{end}} field {{if .Err_Email}}error{{end}}">
 					<label for="email">{{.i18n.Tr "email"}}</label>
-					<input id="email" name="email" value="{{.SignedUser.Email}}">
+					<input id="email" name="email" value="{{.SignedUser.Email}}" {{if .DisableLocalUserManagement}}readonly{{end}}>
 				</div>
 				<div class="inline field">
 					<div class="ui checkbox" id="keep-email-private">

From eca35631ecb44cdec697301de0d373efc314491f Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Wed, 7 Oct 2020 16:43:14 +0200
Subject: [PATCH 02/11] DISABLE_LOCAL_USER_MANAGEMENT mode added

This patch blocks access to local user management options that
are not required and possibly problematic in scenario when users
are managed in external database (like LDAP) and should not be
managed separately in gitea. Options specific to gitea (like
restricted users) are still managed in this app.

Author-Change-Id: IB#1105051
---
 models/user.go                       | 57 +++++++++++++++++++---------
 routers/admin/admin.go               |  4 ++
 routers/admin/auths.go               | 30 +++++++++++++++
 routers/admin/emails.go              | 13 +++++++
 routers/admin/hooks.go               |  1 +
 routers/admin/notice.go              |  1 +
 routers/admin/orgs.go                |  1 +
 routers/admin/repos.go               |  1 +
 routers/admin/users.go               | 21 ++++++++++
 routers/user/setting/account.go      | 35 +++++++++++++++++
 routers/user/setting/profile.go      |  5 ++-
 templates/admin/navbar.tmpl          |  2 +
 templates/admin/user/edit.tmpl       | 20 +++++-----
 templates/admin/user/list.tmpl       |  2 +
 templates/user/settings/account.tmpl | 13 ++++++-
 15 files changed, 175 insertions(+), 31 deletions(-)

diff --git a/models/user.go b/models/user.go
index 3688087782127..ab8681bfff571 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1126,7 +1126,43 @@ func checkDupEmail(e Engine, u *User) error {
 	return nil
 }
 
+// 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 {
+		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(e Engine, u *User) error {
+	if err := updateUserAllowed(u); err != nil {
+		return err
+	}
 	_, err := e.ID(u.ID).AllCols().Update(u)
 	return err
 }
@@ -1142,30 +1178,15 @@ func UpdateUserCols(u *User, cols ...string) error {
 }
 
 func updateUserCols(e Engine, u *User, cols ...string) error {
+	if err := updateUserAllowed(u); err != nil {
+		return err
+	}
 	_, err := e.ID(u.ID).Cols(cols...).Update(u)
 	return err
 }
 
 // UpdateUserSetting updates user's settings.
 func UpdateUserSetting(u *User) error {
-
-	// Don't allow username, fullname nor email changes if local user management is disabled.
-	if setting.Service.DisableLocalUserManagement {
-		if currUser, err := GetUserByID(u.ID); err == nil {
-			if currUser.Name != u.Name {
-				return fmt.Errorf("cannot change %s username; local user management disabled", u.Name)
-			}
-			if currUser.FullName != u.FullName {
-				return fmt.Errorf("cannot change %s full name; local user management disabled", u.Name)
-			}
-			if currUser.Email != u.Email {
-				return fmt.Errorf("cannot change %s e-mail; local user management disabled", u.Name)
-			}
-		} else {
-			return err
-		}
-	}
-
 	if !u.IsOrganization() {
 		if err := checkDupEmail(x, u); err != nil {
 			return err
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index 5dbc321e9db2e..446a150cabc64 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -127,6 +127,7 @@ func Dashboard(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminDashboard"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 	ctx.Data["Stats"] = models.GetStatistic()
 	// FIXME: update periodically
 	updateSystemStatus()
@@ -139,6 +140,7 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
 	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminDashboard"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 	ctx.Data["Stats"] = models.GetStatistic()
 	updateSystemStatus()
 	ctx.Data["SysStatus"] = sysStatus
@@ -235,6 +237,7 @@ func Config(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.config")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminConfig"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	ctx.Data["CustomConf"] = setting.CustomConf
 	ctx.Data["AppUrl"] = setting.AppURL
@@ -325,6 +328,7 @@ func Monitor(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.monitor")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminMonitor"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 	ctx.Data["Processes"] = process.GetManager().Processes()
 	ctx.Data["Entries"] = cron.ListTasks()
 	ctx.Data["Queues"] = queue.GetManager().ManagedQueues()
diff --git a/routers/admin/auths.go b/routers/admin/auths.go
index 98f6e25b1f938..e1066c90ac8e4 100644
--- a/routers/admin/auths.go
+++ b/routers/admin/auths.go
@@ -40,6 +40,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 /admin/auths page denied; local user management disabled"))
+		return
+	}
+
 	var err error
 	ctx.Data["Sources"], err = models.LoginSources()
 	if err != nil {
@@ -96,6 +102,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 /admin/auths/new page denied; local user management disabled"))
+		return
+	}
+
 	// only the first as default
 	for key := range models.OAuth2Providers {
 		ctx.Data["oauth2_provider"] = key
@@ -218,6 +230,12 @@ func NewAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) {
 	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 models.LoginType(form.Type) {
@@ -290,6 +308,12 @@ func EditAuthSource(ctx *context.Context) {
 	ctx.Data["OAuth2Providers"] = models.OAuth2Providers
 	ctx.Data["OAuth2DefaultCustomURLMappings"] = models.OAuth2DefaultCustomURLMappings
 
+	// No access to this page if local user management is disabled.
+	if setting.Service.DisableLocalUserManagement {
+		ctx.ServerError("EditAuthSource", fmt.Errorf("access to /admin/auths page denied; local user management disabled"))
+		return
+	}
+
 	source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid"))
 	if err != nil {
 		ctx.ServerError("GetLoginSourceByID", err)
@@ -314,6 +338,12 @@ func EditAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) {
 	ctx.Data["OAuth2Providers"] = models.OAuth2Providers
 	ctx.Data["OAuth2DefaultCustomURLMappings"] = models.OAuth2DefaultCustomURLMappings
 
+	// 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 := models.GetLoginSourceByID(ctx.ParamsInt64(":authid"))
 	if err != nil {
 		ctx.ServerError("GetLoginSourceByID", err)
diff --git a/routers/admin/emails.go b/routers/admin/emails.go
index f0b14ce5e59dc..2001f148130be 100644
--- a/routers/admin/emails.go
+++ b/routers/admin/emails.go
@@ -6,6 +6,7 @@ package admin
 
 import (
 	"bytes"
+	"fmt"
 	"net/url"
 
 	"code.gitea.io/gitea/models"
@@ -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 /admin/emails page denied; local user management disabled"))
+		return
+	}
+
 	opts := &models.SearchEmailOptions{
 		ListOptions: models.ListOptions{
 			PageSize: setting.UI.Admin.UserPagingNum,
@@ -112,6 +119,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 := com.StrTo(ctx.Query("uid")).MustInt64()
diff --git a/routers/admin/hooks.go b/routers/admin/hooks.go
index 4697c4d933f5a..b677fda0e6572 100644
--- a/routers/admin/hooks.go
+++ b/routers/admin/hooks.go
@@ -20,6 +20,7 @@ const (
 func DefaultOrSystemWebhooks(ctx *context.Context) {
 	var ws []*models.Webhook
 	var err error
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	// Are we looking at default webhooks?
 	if ctx.Params(":configType") == "hooks" {
diff --git a/routers/admin/notice.go b/routers/admin/notice.go
index ad2bad21630ca..b5029d8edbe5a 100644
--- a/routers/admin/notice.go
+++ b/routers/admin/notice.go
@@ -24,6 +24,7 @@ func Notices(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.notices")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminNotices"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	total := models.CountNotices()
 	page := ctx.QueryInt("page")
diff --git a/routers/admin/orgs.go b/routers/admin/orgs.go
index 627f56eaecdfc..9945e305a7b85 100644
--- a/routers/admin/orgs.go
+++ b/routers/admin/orgs.go
@@ -23,6 +23,7 @@ func Organizations(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.organizations")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminOrganizations"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	routers.RenderUserSearch(ctx, &models.SearchUserOptions{
 		Type: models.UserTypeOrganization,
diff --git a/routers/admin/repos.go b/routers/admin/repos.go
index 10abaf9547ad1..4d433d53dce5c 100644
--- a/routers/admin/repos.go
+++ b/routers/admin/repos.go
@@ -28,6 +28,7 @@ func Repos(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.repositories")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminRepositories"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	routers.RenderRepoSearch(ctx, &routers.RepoSearchOptions{
 		Private:  true,
diff --git a/routers/admin/users.go b/routers/admin/users.go
index 531f81b8b51e4..0d180237099d3 100644
--- a/routers/admin/users.go
+++ b/routers/admin/users.go
@@ -6,6 +6,7 @@
 package admin
 
 import (
+	"fmt"
 	"strings"
 
 	"code.gitea.io/gitea/models"
@@ -32,6 +33,7 @@ func Users(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.users")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	routers.RenderUserSearch(ctx, &models.SearchUserOptions{
 		Type: models.UserTypeIndividual,
@@ -50,6 +52,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 /admin/users/new page denied; local user management disabled"))
+		return
+	}
+
 	sources, err := models.LoginSources()
 	if err != nil {
 		ctx.ServerError("LoginSources", err)
@@ -67,6 +75,12 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
 
+	// Don't allow to create users if local user management is disabled.
+	if setting.Service.DisableLocalUserManagement {
+		ctx.ServerError("NewUserPost", fmt.Errorf("cannot create new user; local user management disabled"))
+		return
+	}
+
 	sources, err := models.LoginSources()
 	if err != nil {
 		ctx.ServerError("LoginSources", err)
@@ -188,6 +202,7 @@ func EditUser(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
 	ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	prepareUserInfo(ctx)
 	if ctx.Written() {
@@ -202,6 +217,7 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
 	ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	u := prepareUserInfo(ctx)
 	if ctx.Written() {
@@ -226,6 +242,11 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
 
 	if len(form.Password) > 0 {
 		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/user/setting/account.go b/routers/user/setting/account.go
index 99e20177bc986..eb5d0d6bdecce 100644
--- a/routers/user/setting/account.go
+++ b/routers/user/setting/account.go
@@ -7,6 +7,7 @@ package setting
 
 import (
 	"errors"
+	"fmt"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/auth"
@@ -28,6 +29,7 @@ func Account(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
 	ctx.Data["Email"] = ctx.User.Email
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	loadAccountData(ctx)
 
@@ -38,6 +40,7 @@ func Account(ctx *context.Context) {
 func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	if ctx.HasError() {
 		loadAccountData(ctx)
@@ -83,9 +86,16 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
 func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	// Make emailaddress primary.
 	if ctx.Query("_method") == "PRIMARY" {
+		// No access to this function if local user management is disabled.
+		if setting.Service.DisableLocalUserManagement {
+			ctx.ServerError("MakeEmailPrimary", fmt.Errorf("access to /user/settings/account/email MakeEmailPrimary function denied; local user management disabled"))
+			return
+		}
+
 		if err := models.MakeEmailPrimary(&models.EmailAddress{ID: ctx.QueryInt64("id")}); err != nil {
 			ctx.ServerError("MakeEmailPrimary", err)
 			return
@@ -97,6 +107,11 @@ func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
 	}
 	// Send activation Email
 	if ctx.Query("_method") == "SENDACTIVATION" {
+		// No access to this function if local user management is disabled.
+		if setting.Service.DisableLocalUserManagement {
+			ctx.ServerError("SendActivation", fmt.Errorf("access to /user/settings/account/email 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")
@@ -161,6 +176,12 @@ func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
 		return
 	}
 
+	// No access to this function if local user management is disabled.
+	if setting.Service.DisableLocalUserManagement {
+		ctx.ServerError("AddEmailAddress", fmt.Errorf("access to /user/settings/account/email AddEmailAddress function denied; local user management disabled"))
+		return
+	}
+
 	if ctx.HasError() {
 		loadAccountData(ctx)
 
@@ -201,6 +222,13 @@ func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
 
 // 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 /user/settings/account/email DeleteEmail function denied; local user management disabled"))
+		return
+	}
+
 	if err := models.DeleteEmailAddress(&models.EmailAddress{ID: ctx.QueryInt64("id"), UID: ctx.User.ID}); err != nil {
 		ctx.ServerError("DeleteEmail", err)
 		return
@@ -215,6 +243,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 /user/settings/account/delete page denied; local user management disabled"))
+		return
+	}
+
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
 
@@ -251,6 +285,7 @@ func UpdateUIThemePost(ctx *context.Context, form auth.UpdateThemeForm) {
 
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
+	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	if ctx.HasError() {
 		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go
index 98f852d87a3ab..a230ae8b61113 100644
--- a/routers/user/setting/profile.go
+++ b/routers/user/setting/profile.go
@@ -41,12 +41,15 @@ func Profile(ctx *context.Context) {
 
 func handleUsernameChange(ctx *context.Context, newName string) {
 	// Non-local users are not allowed to change their username.
-	if len(newName) == 0 || !ctx.User.IsLocal() || setting.Service.DisableLocalUserManagement {
+	if len(newName) == 0 || !ctx.User.IsLocal() {
 		return
 	}
 
 	// Check if user name has been changed
 	if ctx.User.LowerName != strings.ToLower(newName) {
+		if setting.Service.DisableLocalUserManagement {
+			ctx.ServerError("ChangeUserName", fmt.Errorf("cannot change user %s username; local user management disabled", ctx.User.Name))
+		}
 		if err := models.ChangeUserName(ctx.User, newName); err != nil {
 			switch {
 			case models.IsErrUserAlreadyExist(err):
diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl
index 6d81d7557f607..0c17fc71ded22 100644
--- a/templates/admin/navbar.tmpl
+++ b/templates/admin/navbar.tmpl
@@ -17,12 +17,14 @@
 	<a class="{{if .PageIsAdminSystemHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/system-hooks">
 		{{.i18n.Tr "admin.systemhooks"}}
 	</a>
+{{if not .DisableLocalUserManagement}}
 	<a class="{{if .PageIsAdminAuthentications}}active{{end}} item" href="{{AppSubUrl}}/admin/auths">
 		{{.i18n.Tr "admin.authentication"}}
 	</a>
 	<a class="{{if .PageIsAdminEmails}}active{{end}} item" href="{{AppSubUrl}}/admin/emails">
 		{{.i18n.Tr "admin.emails"}}
 	</a>
+{{end}}
 	<a class="{{if .PageIsAdminConfig}}active{{end}} item" href="{{AppSubUrl}}/admin/config">
 		{{.i18n.Tr "admin.config"}}
 	</a>
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index 042c09954a2c3..d6177435f4085 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -14,7 +14,7 @@
 					<span>{{.User.Name}}</span>
 				</div>
 				<!-- Types and name -->
-				<div class="inline required field {{if .Err_LoginType}}error{{end}}">
+				<div class="inline required field {{if .Err_LoginType}}error{{end}}{{if .DisableLocalUserManagement}} hide{{end}}">
 					<label>{{.i18n.Tr "admin.users.auth_source"}}</label>
 					<div class="ui selection type dropdown">
 						<input type="hidden" id="login_type" name="login_type" value="{{.LoginSource.Type}}-{{.LoginSource.ID}}" required>
@@ -28,20 +28,20 @@
 						</div>
 					</div>
 				</div>
-				<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}hide{{end}}">
+				<div class="required non-local field {{if .Err_LoginName}}error{{end}}{{if or (eq .User.LoginSource 0) (.DisableLocalUserManagement)}} hide{{end}}">
 					<label for="login_name">{{.i18n.Tr "admin.users.auth_login_name"}}</label>
-					<input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus>
+					<input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus {{if .DisableLocalUserManagement}}readonly{{end}}>
 				</div>
 				<div class="field {{if .Err_FullName}}error{{end}}">
 					<label for="full_name">{{.i18n.Tr "settings.full_name"}}</label>
-					<input id="full_name" name="full_name" value="{{.User.FullName}}">
+					<input id="full_name" name="full_name" value="{{.User.FullName}}" {{if .DisableLocalUserManagement}}readonly{{end}}>
 				</div>
-				<div class="required field {{if .Err_Email}}error{{end}}">
+				<div class="{{if not .DisableLocalUserManagement}}required {{end}}field {{if .Err_Email}}error{{end}}">
 					<label for="email">{{.i18n.Tr "email"}}</label>
-					<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required>
+					<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required {{if .DisableLocalUserManagement}}readonly{{end}}>
 				</div>
 				<input class="fake" type="password">
-				<div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}hide{{end}}">
+				<div class="local field {{if .Err_Password}}error{{end}} {{if or (not (or (.User.IsLocal) (.User.IsOAuth2))) (.DisableLocalUserManagement)}}hide{{end}}">
 					<label for="password">{{.i18n.Tr "password"}}</label>
 					<input id="password" name="password" type="password">
 					<p class="help">{{.i18n.Tr "admin.users.password_helper"}}</p>
@@ -68,19 +68,19 @@
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.is_activated"}}</strong></label>
-						<input name="active" type="checkbox" {{if .User.IsActive}}checked{{end}}>
+						<input name="active" type="checkbox" {{if .User.IsActive}}checked{{end}} {{if .DisableLocalUserManagement}}readonly{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.prohibit_login"}}</strong></label>
-						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if (eq .User.ID .SignedUserID)}}disabled{{end}}>
+						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if or (eq .User.ID .SignedUserID) (.DisableLocalUserManagement)}}disabled{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.is_admin"}}</strong></label>
-						<input name="admin" type="checkbox" {{if .User.IsAdmin}}checked{{end}}>
+						<input name="admin" type="checkbox" {{if .User.IsAdmin}}checked{{end}} {{if .DisableLocalUserManagement}}readonly{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl
index d6dd7d5c03969..3f6fb7e98ec72 100644
--- a/templates/admin/user/list.tmpl
+++ b/templates/admin/user/list.tmpl
@@ -5,9 +5,11 @@
 		{{template "base/alert" .}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "admin.users.user_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}})
+{{if not .DisableLocalUserManagement}}
 			<div class="ui right">
 				<a class="ui blue tiny button" href="{{AppSubUrl}}/admin/users/new">{{.i18n.Tr "admin.users.new_account"}}</a>
 			</div>
+{{end}}
 		</h4>
 		<div class="ui attached segment">
 			{{template "admin/base/search" .}}
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl
index b2cff8618878a..7230c6a6eabb7 100644
--- a/templates/user/settings/account.tmpl
+++ b/templates/user/settings/account.tmpl
@@ -3,6 +3,7 @@
 	{{template "user/settings/navbar" .}}
 	<div class="ui container">
 		{{template "base/alert" .}}
+{{if not .DisableLocalUserManagement}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "settings.password"}}
 		</h4>
@@ -36,7 +37,7 @@
 			</div>
 			{{end}}
 		</div>
-
+{{end}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "settings.manage_emails"}}
 		</h4>
@@ -70,6 +71,7 @@
 				</div>
 				{{range .Emails}}
 					<div class="item">
+{{if not $.DisableLocalUserManagement}}
 						{{if not .IsPrimary}}
 							<div class="right floated content">
 								<button class="ui red tiny button delete-button" id="delete-email" data-url="{{AppSubUrl}}/user/settings/account/email/delete" data-id="{{.ID}}">
@@ -101,8 +103,11 @@
 								</form>
 							</div>
 						{{end}}
+{{end}}
 						<div class="content">
 							<strong>{{.Email}}</strong>
+{{if not $.DisableLocalUserManagement}}
+
 							{{if .IsPrimary}}
 								<div class="ui blue label">{{$.i18n.Tr "settings.primary"}}</div>
 							{{end}}
@@ -111,11 +116,13 @@
 							{{else}}
 								<div class="ui label">{{$.i18n.Tr "settings.requires_activation"}}</div>
 							{{end}}
+{{end}}
 						</div>
 					</div>
 				{{end}}
 			</div>
 		</div>
+{{if not .DisableLocalUserManagement}}
 		<div class="ui attached bottom segment">
 			<form class="ui form" action="{{AppSubUrl}}/user/settings/account/email" method="post">
 				{{.CsrfTokenHtml}}
@@ -128,7 +135,7 @@
 				</button>
 			</form>
 		</div>
-
+{{end}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "settings.manage_themes"}}
 		</h4>
@@ -167,6 +174,7 @@
 			</form>
 			</div>
 		</div>
+{{if not .DisableLocalUserManagement}}
 		<h4 class="ui top attached warning header">
 			{{.i18n.Tr "settings.delete_account"}}
 		</h4>
@@ -189,6 +197,7 @@
 				</div>
 			</form>
 		</div>
+{{end}}
 	</div>
 </div>
 

From f92d339e4684a2ecfe5786112d88e3e46781ee35 Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Wed, 7 Oct 2020 19:43:39 +0200
Subject: [PATCH 03/11] Fixed external user syncing when local user management
 is disabled

This fixes external user syncing when local user management is disabled.

Fixes: eca35631ecb44cdec697301de0d373efc314491f
Author-Change-Id: IB#1105051
---
 cmd/admin.go                    |  2 +-
 models/login_source.go          |  4 ++--
 models/repo.go                  |  2 +-
 models/user.go                  | 22 ++++++++++++----------
 models/user_mail.go             |  4 ++--
 modules/auth/sso/sso.go         |  2 +-
 routers/api/v1/org/org.go       |  2 +-
 routers/user/auth.go            | 16 ++++++++--------
 routers/user/auth_openid.go     |  2 +-
 routers/user/setting/account.go |  2 +-
 routers/user/setting/profile.go |  2 +-
 11 files changed, 31 insertions(+), 29 deletions(-)

diff --git a/cmd/admin.go b/cmd/admin.go
index 9f81f5284dd6d..813b92a3d0f7a 100644
--- a/cmd/admin.go
+++ b/cmd/admin.go
@@ -283,7 +283,7 @@ func runChangePassword(c *cli.Context) error {
 	}
 	user.HashPassword(c.String("password"))
 
-	if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil {
+	if err := models.UpdateUserCols(user, false, "passwd", "salt"); err != nil {
 		return err
 	}
 
diff --git a/models/login_source.go b/models/login_source.go
index 1de24a9cf70b5..55efa3c0c62c5 100644
--- a/models/login_source.go
+++ b/models/login_source.go
@@ -488,7 +488,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*Use
 				cols = append(cols, "is_restricted")
 			}
 			if len(cols) > 0 {
-				err = UpdateUserCols(user, cols...)
+				err = UpdateUserCols(user, false, cols...)
 				if err != nil {
 					return nil, err
 				}
@@ -768,7 +768,7 @@ func UserSignIn(username, password string) (*User, error) {
 				// Update password hash if server password hash algorithm have changed
 				if user.PasswdHashAlgo != setting.PasswordHashAlgo {
 					user.HashPassword(password)
-					if err := UpdateUserCols(user, "passwd", "passwd_hash_algo"); err != nil {
+					if err := UpdateUserCols(user, false, "passwd", "passwd_hash_algo"); err != nil {
 						return nil, err
 					}
 				}
diff --git a/models/repo.go b/models/repo.go
index 46f91fc7df658..20822ff14c405 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -1190,7 +1190,7 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO
 
 	// Remember visibility preference.
 	u.LastRepoVisibility = repo.IsPrivate
-	if err = updateUserCols(ctx.e, u, "last_repo_visibility"); err != nil {
+	if err = updateUserCols(ctx.e, u, false, "last_repo_visibility"); err != nil {
 		return fmt.Errorf("updateUser: %v", err)
 	}
 
diff --git a/models/user.go b/models/user.go
index ab8681bfff571..966dc1e023411 100644
--- a/models/user.go
+++ b/models/user.go
@@ -220,13 +220,13 @@ func (u *User) SetLastLogin() {
 // UpdateDiffViewStyle updates the users diff view style
 func (u *User) UpdateDiffViewStyle(style string) error {
 	u.DiffViewStyle = style
-	return UpdateUserCols(u, "diff_view_style")
+	return UpdateUserCols(u, false, "diff_view_style")
 }
 
 // UpdateTheme updates a users' theme irrespective of the site wide theme
 func (u *User) UpdateTheme(themeName string) error {
 	u.Theme = themeName
-	return UpdateUserCols(u, "theme")
+	return UpdateUserCols(u, false, "theme")
 }
 
 // GetEmail returns an noreply email, if the user has set to keep his
@@ -824,7 +824,7 @@ func (u *User) EmailNotifications() string {
 // SetEmailNotifications sets the user's email notification preference
 func (u *User) SetEmailNotifications(set string) error {
 	u.EmailNotificationsPreference = set
-	if err := UpdateUserCols(u, "email_notifications_preference"); err != nil {
+	if err := UpdateUserCols(u, false, "email_notifications_preference"); err != nil {
 		log.Error("SetEmailNotifications: %v", err)
 		return err
 	}
@@ -1173,13 +1173,15 @@ func UpdateUser(u *User) error {
 }
 
 // UpdateUserCols update user according special columns
-func UpdateUserCols(u *User, cols ...string) error {
-	return updateUserCols(x, u, cols...)
+func UpdateUserCols(u *User, force bool, cols ...string) error {
+	return updateUserCols(x, u, force, cols...)
 }
 
-func updateUserCols(e Engine, u *User, cols ...string) error {
-	if err := updateUserAllowed(u); err != nil {
-		return err
+func updateUserCols(e Engine, u *User, force bool, cols ...string) error {
+	if !force {
+		if err := updateUserAllowed(u); err != nil {
+			return err
+		}
 	}
 	_, err := e.ID(u.ID).Cols(cols...).Update(u)
 	return err
@@ -2021,7 +2023,7 @@ func SyncExternalUsers(ctx context.Context, updateExisting bool) error {
 						}
 						usr.IsActive = true
 
-						err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_restricted", "is_active")
+						err = UpdateUserCols(usr, true, "full_name", "email", "is_admin", "is_restricted", "is_active")
 						if err != nil {
 							log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err)
 						}
@@ -2058,7 +2060,7 @@ func SyncExternalUsers(ctx context.Context, updateExisting bool) error {
 						log.Trace("SyncExternalUsers[%s]: Deactivating user %s", s.Name, usr.Name)
 
 						usr.IsActive = false
-						err = UpdateUserCols(usr, "is_active")
+						err = UpdateUserCols(usr, true, "is_active")
 						if err != nil {
 							log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", s.Name, usr.Name, err)
 						}
diff --git a/models/user_mail.go b/models/user_mail.go
index 60354e23ffb22..390e60f9fa16d 100644
--- a/models/user_mail.go
+++ b/models/user_mail.go
@@ -201,7 +201,7 @@ func (email *EmailAddress) updateActivation(e Engine, activate bool) error {
 	if _, err := e.ID(email.ID).Cols("is_activated").Update(email); err != nil {
 		return err
 	}
-	return updateUserCols(e, user, "rands")
+	return updateUserCols(e, user, false, "rands")
 }
 
 // DeleteEmailAddress deletes an email address of given user.
@@ -448,7 +448,7 @@ func ActivateUserEmail(userID int64, email string, primary, activate bool) (err
 		if user.Rands, err = GetUserSalt(); err != nil {
 			return fmt.Errorf("generate salt: %v", err)
 		}
-		if err = updateUserCols(sess, &user, "is_active", "rands"); err != nil {
+		if err = updateUserCols(sess, &user, false, "is_active", "rands"); err != nil {
 			return fmt.Errorf("updateUserCols(): %v", err)
 		}
 	} else {
diff --git a/modules/auth/sso/sso.go b/modules/auth/sso/sso.go
index c2e36f3f5ebf2..f8d999ee891b7 100644
--- a/modules/auth/sso/sso.go
+++ b/modules/auth/sso/sso.go
@@ -133,7 +133,7 @@ func handleSignIn(ctx *macaron.Context, sess session.Store, user *models.User) {
 	// If the user does not have a locale set, we save the current one.
 	if len(user.Language) == 0 {
 		user.Language = ctx.Locale.Language()
-		if err := models.UpdateUserCols(user, "language"); err != nil {
+		if err := models.UpdateUserCols(user, false, "language"); err != nil {
 			log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", user.ID, user.Language))
 			return
 		}
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index 987f141078088..4759c8ede1667 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -253,7 +253,7 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
 	if form.Visibility != "" {
 		org.Visibility = api.VisibilityModes[form.Visibility]
 	}
-	if err := models.UpdateUserCols(org, "full_name", "description", "website", "location", "visibility"); err != nil {
+	if err := models.UpdateUserCols(org, false, "full_name", "description", "website", "location", "visibility"); err != nil {
 		ctx.Error(http.StatusInternalServerError, "EditOrganization", err)
 		return
 	}
diff --git a/routers/user/auth.go b/routers/user/auth.go
index 96a73c9dd4632..9eb8cd1359454 100644
--- a/routers/user/auth.go
+++ b/routers/user/auth.go
@@ -519,7 +519,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 	// If the user does not have a locale set, we save the current one.
 	if len(u.Language) == 0 {
 		u.Language = ctx.Locale.Language()
-		if err := models.UpdateUserCols(u, "language"); err != nil {
+		if err := models.UpdateUserCols(u, false, "language"); err != nil {
 			log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
 			return setting.AppSubURL + "/"
 		}
@@ -532,7 +532,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 
 	// Register last login
 	u.SetLastLogin()
-	if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
+	if err := models.UpdateUserCols(u, false, "last_login_unix"); err != nil {
 		ctx.ServerError("UpdateUserCols", err)
 		return setting.AppSubURL + "/"
 	}
@@ -639,7 +639,7 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
 
 		// Register last login
 		u.SetLastLogin()
-		if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
+		if err := models.UpdateUserCols(u, false, "last_login_unix"); err != nil {
 			ctx.ServerError("UpdateUserCols", err)
 			return
 		}
@@ -978,7 +978,7 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
 		u.IsAdmin = true
 		u.IsActive = true
 		u.SetLastLogin()
-		if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil {
+		if err := models.UpdateUserCols(u, false, "is_admin", "is_active", "last_login_unix"); err != nil {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
@@ -1154,7 +1154,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
 		u.IsAdmin = true
 		u.IsActive = true
 		u.SetLastLogin()
-		if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil {
+		if err := models.UpdateUserCols(u, false, "is_admin", "is_active", "last_login_unix"); err != nil {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
@@ -1215,7 +1215,7 @@ func Activate(ctx *context.Context) {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
-		if err := models.UpdateUserCols(user, "is_active", "rands"); err != nil {
+		if err := models.UpdateUserCols(user, false, "is_active", "rands"); err != nil {
 			if models.IsErrUserNotExist(err) {
 				ctx.Error(404)
 			} else {
@@ -1475,7 +1475,7 @@ func ResetPasswdPost(ctx *context.Context) {
 	}
 	u.HashPassword(passwd)
 	u.MustChangePassword = false
-	if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
+	if err := models.UpdateUserCols(u, false, "must_change_password", "passwd", "rands", "salt"); err != nil {
 		ctx.ServerError("UpdateUser", err)
 		return
 	}
@@ -1551,7 +1551,7 @@ func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form aut
 	u.HashPassword(form.Password)
 	u.MustChangePassword = false
 
-	if err := models.UpdateUserCols(u, "must_change_password", "passwd", "salt"); err != nil {
+	if err := models.UpdateUserCols(u, false, "must_change_password", "passwd", "salt"); err != nil {
 		ctx.ServerError("UpdateUser", err)
 		return
 	}
diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go
index ba2c8be8c24cb..258691522874a 100644
--- a/routers/user/auth_openid.go
+++ b/routers/user/auth_openid.go
@@ -443,7 +443,7 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
 		u.IsAdmin = true
 		u.IsActive = true
 		u.SetLastLogin()
-		if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil {
+		if err := models.UpdateUserCols(u, false, "is_admin", "is_active", "last_login_unix"); err != nil {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go
index eb5d0d6bdecce..a956e04a037c2 100644
--- a/routers/user/setting/account.go
+++ b/routers/user/setting/account.go
@@ -71,7 +71,7 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
 			return
 		}
 		ctx.User.HashPassword(form.Password)
-		if err := models.UpdateUserCols(ctx.User, "salt", "passwd"); err != nil {
+		if err := models.UpdateUserCols(ctx.User, false, "salt", "passwd"); err != nil {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go
index a230ae8b61113..0207296fc887e 100644
--- a/routers/user/setting/profile.go
+++ b/routers/user/setting/profile.go
@@ -160,7 +160,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo
 		}
 	}
 
-	if err := models.UpdateUserCols(ctxUser, "avatar", "avatar_email", "use_custom_avatar"); err != nil {
+	if err := models.UpdateUserCols(ctxUser, false, "avatar", "avatar_email", "use_custom_avatar"); err != nil {
 		return fmt.Errorf("UpdateUser: %v", err)
 	}
 

From 70672ee67fbdb8b9a4d14e7b9814f912608cdb6e Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Wed, 7 Oct 2020 20:42:28 +0200
Subject: [PATCH 04/11] Hide message about changing username

Hide message about changing username when local user modifications are disabled.

Author-Change-Id: IB#1105051
---
 templates/user/settings/profile.tmpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index dd55de94695c0..28af65ab25ea5 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -13,7 +13,7 @@
 				<div class="{{if not .DisableLocalUserManagement}}required{{end}} field {{if .Err_Name}}error{{end}}">
 					<label for="username">{{.i18n.Tr "username"}}<span class="text red hide" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span></label>
 					<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus {{if not .DisableLocalUserManagement}}required{{end}} {{if or (not .SignedUser.IsLocal) (.DisableLocalUserManagement) }}disabled{{end}}>
-					{{if not .SignedUser.IsLocal}}
+					{{if not (or (.SignedUser.IsLocal) (.DisableLocalUserManagement))}}
 					<p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p>
 					{{end}}
 				</div>

From d97a8ea9a07a1d37ceba05b2aba5befac25c68d2 Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Tue, 20 Oct 2020 23:12:05 +0200
Subject: [PATCH 05/11] Fixed DISABLE_LOCAL_USER_MANAGEMENT mode

Adopted repos screens didn't hide menu tabs. This
mod fixes it and simplifies configuration for templates.
It also removes unnecessarry comment.

Author-Change-Id: IB#1105051
Fixes: eca35631ecb44cdec697301de0d373efc314491f
---
 modules/templates/helper.go          |  3 +++
 routers/admin/admin.go               |  4 ----
 routers/admin/hooks.go               |  1 -
 routers/admin/notice.go              |  1 -
 routers/admin/orgs.go                |  1 -
 routers/admin/repos.go               |  1 -
 routers/admin/users.go               | 11 ++++-------
 routers/user/setting/account.go      |  4 ----
 routers/user/setting/profile.go      |  2 --
 templates/admin/navbar.tmpl          |  2 +-
 templates/admin/user/edit.tmpl       | 20 ++++++++++----------
 templates/admin/user/list.tmpl       |  2 +-
 templates/user/settings/account.tmpl | 10 +++++-----
 templates/user/settings/profile.tmpl | 12 ++++++------
 14 files changed, 30 insertions(+), 44 deletions(-)

diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 63be27d98735a..13a185eb50d40 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -228,6 +228,9 @@ func NewFuncMap() []template.FuncMap {
 		"DisableImportLocal": func() bool {
 			return !setting.ImportLocalPaths
 		},
+		"DisableLocalUserManagement": func() bool {
+			return setting.Service.DisableLocalUserManagement
+		},
 		"TrN": TrN,
 		"Dict": func(values ...interface{}) (map[string]interface{}, error) {
 			if len(values)%2 != 0 {
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index 446a150cabc64..5dbc321e9db2e 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -127,7 +127,6 @@ func Dashboard(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminDashboard"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 	ctx.Data["Stats"] = models.GetStatistic()
 	// FIXME: update periodically
 	updateSystemStatus()
@@ -140,7 +139,6 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
 	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminDashboard"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 	ctx.Data["Stats"] = models.GetStatistic()
 	updateSystemStatus()
 	ctx.Data["SysStatus"] = sysStatus
@@ -237,7 +235,6 @@ func Config(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.config")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminConfig"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	ctx.Data["CustomConf"] = setting.CustomConf
 	ctx.Data["AppUrl"] = setting.AppURL
@@ -328,7 +325,6 @@ func Monitor(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.monitor")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminMonitor"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 	ctx.Data["Processes"] = process.GetManager().Processes()
 	ctx.Data["Entries"] = cron.ListTasks()
 	ctx.Data["Queues"] = queue.GetManager().ManagedQueues()
diff --git a/routers/admin/hooks.go b/routers/admin/hooks.go
index b677fda0e6572..4697c4d933f5a 100644
--- a/routers/admin/hooks.go
+++ b/routers/admin/hooks.go
@@ -20,7 +20,6 @@ const (
 func DefaultOrSystemWebhooks(ctx *context.Context) {
 	var ws []*models.Webhook
 	var err error
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	// Are we looking at default webhooks?
 	if ctx.Params(":configType") == "hooks" {
diff --git a/routers/admin/notice.go b/routers/admin/notice.go
index b5029d8edbe5a..ad2bad21630ca 100644
--- a/routers/admin/notice.go
+++ b/routers/admin/notice.go
@@ -24,7 +24,6 @@ func Notices(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.notices")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminNotices"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	total := models.CountNotices()
 	page := ctx.QueryInt("page")
diff --git a/routers/admin/orgs.go b/routers/admin/orgs.go
index 9945e305a7b85..627f56eaecdfc 100644
--- a/routers/admin/orgs.go
+++ b/routers/admin/orgs.go
@@ -23,7 +23,6 @@ func Organizations(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.organizations")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminOrganizations"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	routers.RenderUserSearch(ctx, &models.SearchUserOptions{
 		Type: models.UserTypeOrganization,
diff --git a/routers/admin/repos.go b/routers/admin/repos.go
index 4d433d53dce5c..10abaf9547ad1 100644
--- a/routers/admin/repos.go
+++ b/routers/admin/repos.go
@@ -28,7 +28,6 @@ func Repos(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.repositories")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminRepositories"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	routers.RenderRepoSearch(ctx, &routers.RepoSearchOptions{
 		Private:  true,
diff --git a/routers/admin/users.go b/routers/admin/users.go
index 0d180237099d3..dc21921da143f 100644
--- a/routers/admin/users.go
+++ b/routers/admin/users.go
@@ -33,7 +33,6 @@ func Users(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("admin.users")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	routers.RenderUserSearch(ctx, &models.SearchUserOptions{
 		Type: models.UserTypeIndividual,
@@ -202,7 +201,6 @@ func EditUser(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
 	ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	prepareUserInfo(ctx)
 	if ctx.Written() {
@@ -217,7 +215,6 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
 	ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminUsers"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	u := prepareUserInfo(ctx)
 	if ctx.Written() {
@@ -243,10 +240,10 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
 	if len(form.Password) > 0 {
 		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 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/user/setting/account.go b/routers/user/setting/account.go
index a956e04a037c2..f9fe5c00fc4c6 100644
--- a/routers/user/setting/account.go
+++ b/routers/user/setting/account.go
@@ -29,7 +29,6 @@ func Account(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
 	ctx.Data["Email"] = ctx.User.Email
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	loadAccountData(ctx)
 
@@ -40,7 +39,6 @@ func Account(ctx *context.Context) {
 func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	if ctx.HasError() {
 		loadAccountData(ctx)
@@ -86,7 +84,6 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
 func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	// Make emailaddress primary.
 	if ctx.Query("_method") == "PRIMARY" {
@@ -285,7 +282,6 @@ func UpdateUIThemePost(ctx *context.Context, form auth.UpdateThemeForm) {
 
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsAccount"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	if ctx.HasError() {
 		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go
index 0207296fc887e..de80f013a62e5 100644
--- a/routers/user/setting/profile.go
+++ b/routers/user/setting/profile.go
@@ -34,7 +34,6 @@ const (
 func Profile(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsProfile"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	ctx.HTML(200, tplSettingsProfile)
 }
@@ -84,7 +83,6 @@ func handleUsernameChange(ctx *context.Context, newName string) {
 func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsProfile"] = true
-	ctx.Data["DisableLocalUserManagement"] = setting.Service.DisableLocalUserManagement
 
 	if ctx.HasError() {
 		ctx.HTML(200, tplSettingsProfile)
diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl
index 0c17fc71ded22..eba96209c0966 100644
--- a/templates/admin/navbar.tmpl
+++ b/templates/admin/navbar.tmpl
@@ -17,7 +17,7 @@
 	<a class="{{if .PageIsAdminSystemHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/system-hooks">
 		{{.i18n.Tr "admin.systemhooks"}}
 	</a>
-{{if not .DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 	<a class="{{if .PageIsAdminAuthentications}}active{{end}} item" href="{{AppSubUrl}}/admin/auths">
 		{{.i18n.Tr "admin.authentication"}}
 	</a>
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index d6177435f4085..6031f6895098a 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -14,7 +14,7 @@
 					<span>{{.User.Name}}</span>
 				</div>
 				<!-- Types and name -->
-				<div class="inline required field {{if .Err_LoginType}}error{{end}}{{if .DisableLocalUserManagement}} hide{{end}}">
+				<div class="inline required field {{if .Err_LoginType}}error{{end}}{{if DisableLocalUserManagement}} hide{{end}}">
 					<label>{{.i18n.Tr "admin.users.auth_source"}}</label>
 					<div class="ui selection type dropdown">
 						<input type="hidden" id="login_type" name="login_type" value="{{.LoginSource.Type}}-{{.LoginSource.ID}}" required>
@@ -28,20 +28,20 @@
 						</div>
 					</div>
 				</div>
-				<div class="required non-local field {{if .Err_LoginName}}error{{end}}{{if or (eq .User.LoginSource 0) (.DisableLocalUserManagement)}} hide{{end}}">
+				<div class="required non-local field {{if .Err_LoginName}}error{{end}}{{if or (eq .User.LoginSource 0) DisableLocalUserManagement}} hide{{end}}">
 					<label for="login_name">{{.i18n.Tr "admin.users.auth_login_name"}}</label>
-					<input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus {{if .DisableLocalUserManagement}}readonly{{end}}>
+					<input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus {{if DisableLocalUserManagement}}readonly{{end}}>
 				</div>
 				<div class="field {{if .Err_FullName}}error{{end}}">
 					<label for="full_name">{{.i18n.Tr "settings.full_name"}}</label>
-					<input id="full_name" name="full_name" value="{{.User.FullName}}" {{if .DisableLocalUserManagement}}readonly{{end}}>
+					<input id="full_name" name="full_name" value="{{.User.FullName}}" {{if DisableLocalUserManagement}}readonly{{end}}>
 				</div>
-				<div class="{{if not .DisableLocalUserManagement}}required {{end}}field {{if .Err_Email}}error{{end}}">
+				<div class="{{if not DisableLocalUserManagement}}required {{end}}field {{if .Err_Email}}error{{end}}">
 					<label for="email">{{.i18n.Tr "email"}}</label>
-					<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required {{if .DisableLocalUserManagement}}readonly{{end}}>
+					<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required {{if DisableLocalUserManagement}}readonly{{end}}>
 				</div>
 				<input class="fake" type="password">
-				<div class="local field {{if .Err_Password}}error{{end}} {{if or (not (or (.User.IsLocal) (.User.IsOAuth2))) (.DisableLocalUserManagement)}}hide{{end}}">
+				<div class="local field {{if .Err_Password}}error{{end}} {{if or (not (or (.User.IsLocal) (.User.IsOAuth2))) DisableLocalUserManagement}}hide{{end}}">
 					<label for="password">{{.i18n.Tr "password"}}</label>
 					<input id="password" name="password" type="password">
 					<p class="help">{{.i18n.Tr "admin.users.password_helper"}}</p>
@@ -68,19 +68,19 @@
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.is_activated"}}</strong></label>
-						<input name="active" type="checkbox" {{if .User.IsActive}}checked{{end}} {{if .DisableLocalUserManagement}}readonly{{end}}>
+						<input name="active" type="checkbox" {{if .User.IsActive}}checked{{end}} {{if DisableLocalUserManagement}}readonly{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.prohibit_login"}}</strong></label>
-						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if or (eq .User.ID .SignedUserID) (.DisableLocalUserManagement)}}disabled{{end}}>
+						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if or (eq .User.ID .SignedUserID) DisableLocalUserManagement}}disabled{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.is_admin"}}</strong></label>
-						<input name="admin" type="checkbox" {{if .User.IsAdmin}}checked{{end}} {{if .DisableLocalUserManagement}}readonly{{end}}>
+						<input name="admin" type="checkbox" {{if .User.IsAdmin}}checked{{end}} {{if DisableLocalUserManagement}}readonly{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl
index 3f6fb7e98ec72..0363501bbeb0f 100644
--- a/templates/admin/user/list.tmpl
+++ b/templates/admin/user/list.tmpl
@@ -5,7 +5,7 @@
 		{{template "base/alert" .}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "admin.users.user_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}})
-{{if not .DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 			<div class="ui right">
 				<a class="ui blue tiny button" href="{{AppSubUrl}}/admin/users/new">{{.i18n.Tr "admin.users.new_account"}}</a>
 			</div>
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl
index 7230c6a6eabb7..584974cc9b1bb 100644
--- a/templates/user/settings/account.tmpl
+++ b/templates/user/settings/account.tmpl
@@ -3,7 +3,7 @@
 	{{template "user/settings/navbar" .}}
 	<div class="ui container">
 		{{template "base/alert" .}}
-{{if not .DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "settings.password"}}
 		</h4>
@@ -71,7 +71,7 @@
 				</div>
 				{{range .Emails}}
 					<div class="item">
-{{if not $.DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 						{{if not .IsPrimary}}
 							<div class="right floated content">
 								<button class="ui red tiny button delete-button" id="delete-email" data-url="{{AppSubUrl}}/user/settings/account/email/delete" data-id="{{.ID}}">
@@ -106,7 +106,7 @@
 {{end}}
 						<div class="content">
 							<strong>{{.Email}}</strong>
-{{if not $.DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 
 							{{if .IsPrimary}}
 								<div class="ui blue label">{{$.i18n.Tr "settings.primary"}}</div>
@@ -122,7 +122,7 @@
 				{{end}}
 			</div>
 		</div>
-{{if not .DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 		<div class="ui attached bottom segment">
 			<form class="ui form" action="{{AppSubUrl}}/user/settings/account/email" method="post">
 				{{.CsrfTokenHtml}}
@@ -174,7 +174,7 @@
 			</form>
 			</div>
 		</div>
-{{if not .DisableLocalUserManagement}}
+{{if not DisableLocalUserManagement}}
 		<h4 class="ui top attached warning header">
 			{{.i18n.Tr "settings.delete_account"}}
 		</h4>
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 28af65ab25ea5..2748eb45f89be 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -10,20 +10,20 @@
 			<p>{{.i18n.Tr "settings.profile_desc"}}</p>
 			<form class="ui form" action="{{.Link}}" method="post">
 				{{.CsrfTokenHtml}}
-				<div class="{{if not .DisableLocalUserManagement}}required{{end}} field {{if .Err_Name}}error{{end}}">
+				<div class="{{if not DisableLocalUserManagement}}required{{end}} field {{if .Err_Name}}error{{end}}">
 					<label for="username">{{.i18n.Tr "username"}}<span class="text red hide" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span></label>
-					<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus {{if not .DisableLocalUserManagement}}required{{end}} {{if or (not .SignedUser.IsLocal) (.DisableLocalUserManagement) }}disabled{{end}}>
-					{{if not (or (.SignedUser.IsLocal) (.DisableLocalUserManagement))}}
+					<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus {{if not DisableLocalUserManagement}}required{{end}} {{if or (not .SignedUser.IsLocal) DisableLocalUserManagement}}disabled{{end}}>
+					{{if not (or .SignedUser.IsLocal DisableLocalUserManagement)}}
 					<p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p>
 					{{end}}
 				</div>
 				<div class="field {{if .Err_FullName}}error{{end}}">
 					<label for="full_name">{{.i18n.Tr "settings.full_name"}}</label>
-					<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" {{if .DisableLocalUserManagement}}readonly{{end}}>
+					<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" {{if DisableLocalUserManagement}}readonly{{end}}>
 				</div>
-				<div class="{{if not .DisableLocalUserManagement}}required{{end}} field {{if .Err_Email}}error{{end}}">
+				<div class="{{if not DisableLocalUserManagement}}required{{end}} field {{if .Err_Email}}error{{end}}">
 					<label for="email">{{.i18n.Tr "email"}}</label>
-					<input id="email" name="email" value="{{.SignedUser.Email}}" {{if .DisableLocalUserManagement}}readonly{{end}}>
+					<input id="email" name="email" value="{{.SignedUser.Email}}" {{if DisableLocalUserManagement}}readonly{{end}}>
 				</div>
 				<div class="inline field">
 					<div class="ui checkbox" id="keep-email-private">

From fe6ae270f1e7bbb9cfd28c618f69c985168cb9bb Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Tue, 23 Mar 2021 12:27:17 +0100
Subject: [PATCH 06/11] Allow org name change in DISABLE_LOCAL_USER_MANAGEMENT
 mode

Fixes 500 on organization name change in DISABLE_LOCAL_USER_MANAGEMENT mode.

Fixes: eca35631ecb44cdec697301de0d373efc314491f
Author-Change-Id: IB#1105051
---
 models/user.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/models/user.go b/models/user.go
index 966dc1e023411..064e47b296ead 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1129,7 +1129,7 @@ func checkDupEmail(e Engine, u *User) error {
 // 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 {
+	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)

From cda4a8a21624587690a932206362d4f6a6d9bc30 Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Tue, 1 Feb 2022 12:04:52 +0100
Subject: [PATCH 07/11] Separate function for forced update added

Fixes: f92d339e4684a2ecfe5786112d88e3e46781ee35
Related: https://github.com/go-gitea/gitea/pull/18466#discussion_r796044064
Author-Change-Id: IB#1105051
---
 cmd/admin.go                                  |  2 +-
 models/repo.go                                |  2 +-
 models/user/email_address.go                  |  4 ++--
 models/user/user.go                           | 19 ++++++++++++-------
 routers/api/v1/org/org.go                     |  2 +-
 routers/web/auth/auth.go                      | 10 +++++-----
 routers/web/auth/oauth.go                     |  4 ++--
 routers/web/auth/password.go                  |  4 ++--
 routers/web/user/setting/account.go           |  2 +-
 routers/web/user/setting/profile.go           |  2 +-
 services/auth/auth.go                         |  2 +-
 services/auth/source/db/authenticate.go       |  2 +-
 .../auth/source/ldap/source_authenticate.go   |  2 +-
 services/auth/source/ldap/source_sync.go      |  4 ++--
 services/user/user.go                         |  2 +-
 15 files changed, 34 insertions(+), 29 deletions(-)

diff --git a/cmd/admin.go b/cmd/admin.go
index e3c0922680794..3c7f7c8a7c5df 100644
--- a/cmd/admin.go
+++ b/cmd/admin.go
@@ -471,7 +471,7 @@ func runChangePassword(c *cli.Context) error {
 		return err
 	}
 
-	if err = user_model.UpdateUserCols(db.DefaultContext, user, false, "passwd", "passwd_hash_algo", "salt"); err != nil {
+	if err = user_model.UpdateUserCols(db.DefaultContext, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
 		return err
 	}
 
diff --git a/models/repo.go b/models/repo.go
index d48ba23e05ff5..83031c508cdce 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -508,7 +508,7 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_
 
 	// Remember visibility preference.
 	u.LastRepoVisibility = repo.IsPrivate
-	if err = user_model.UpdateUserColsEngine(db.GetEngine(ctx), u, false, "last_repo_visibility"); err != nil {
+	if err = user_model.UpdateUserColsEngine(db.GetEngine(ctx), u, "last_repo_visibility"); err != nil {
 		return fmt.Errorf("updateUser: %v", err)
 	}
 
diff --git a/models/user/email_address.go b/models/user/email_address.go
index 096f120fcf657..0ff62fb6a8056 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -303,7 +303,7 @@ func updateActivation(e db.Engine, email *EmailAddress, activate bool) error {
 	if _, err := e.ID(email.ID).Cols("is_activated").Update(email); err != nil {
 		return err
 	}
-	return UpdateUserColsEngine(e, user, false, "rands")
+	return UpdateUserColsEngine(e, user, "rands")
 }
 
 // MakeEmailPrimary sets primary email address of given user.
@@ -513,7 +513,7 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
 			if user.Rands, err = GetUserSalt(); err != nil {
 				return fmt.Errorf("unable to generate salt: %v", err)
 			}
-			if err = UpdateUserColsEngine(sess, &user, false, "is_active", "rands"); err != nil {
+			if err = UpdateUserColsEngine(sess, &user, "is_active", "rands"); err != nil {
 				return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err)
 			}
 		}
diff --git a/models/user/user.go b/models/user/user.go
index 0c739f758f9b2..037584f1a7af9 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -206,13 +206,13 @@ func (u *User) SetLastLogin() {
 // UpdateUserDiffViewStyle updates the users diff view style
 func UpdateUserDiffViewStyle(u *User, style string) error {
 	u.DiffViewStyle = style
-	return UpdateUserCols(db.DefaultContext, u, false, "diff_view_style")
+	return UpdateUserCols(db.DefaultContext, u, "diff_view_style")
 }
 
 // UpdateUserTheme updates a users' theme irrespective of the site wide theme
 func UpdateUserTheme(u *User, themeName string) error {
 	u.Theme = themeName
-	return UpdateUserCols(db.DefaultContext, u, false, "theme")
+	return UpdateUserCols(db.DefaultContext, u, "theme")
 }
 
 // GetEmail returns an noreply email, if the user has set to keep his
@@ -502,7 +502,7 @@ func (u *User) EmailNotifications() string {
 // SetEmailNotifications sets the user's email notification preference
 func SetEmailNotifications(u *User, set string) error {
 	u.EmailNotificationsPreference = set
-	if err := UpdateUserCols(db.DefaultContext, u, false, "email_notifications_preference"); err != nil {
+	if err := UpdateUserCols(db.DefaultContext, u, "email_notifications_preference"); err != nil {
 		log.Error("SetEmailNotifications: %v", err)
 		return err
 	}
@@ -908,13 +908,18 @@ func UpdateUser(u *User, emailChanged bool) error {
 }
 
 // UpdateUserCols update user according special columns
-func UpdateUserCols(ctx context.Context, u *User, force bool, cols ...string) error {
-	return updateUserCols(db.GetEngine(ctx), u, force, cols...)
+func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
+	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, force bool, cols ...string) error {
-	return updateUserCols(e, u, force, cols...)
+func UpdateUserColsEngine(e db.Engine, u *User, cols ...string) error {
+	return updateUserCols(e, u, false, cols...)
 }
 
 func updateUserCols(e db.Engine, u *User, force bool, cols ...string) error {
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index beac42c4b4a80..133cce3416c5d 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -346,7 +346,7 @@ func Edit(ctx *context.APIContext) {
 	if form.RepoAdminChangeTeamAccess != nil {
 		org.RepoAdminChangeTeamAccess = *form.RepoAdminChangeTeamAccess
 	}
-	if err := user_model.UpdateUserCols(db.DefaultContext, org.AsUser(), false,
+	if err := user_model.UpdateUserCols(db.DefaultContext, org.AsUser(),
 		"full_name", "description", "website", "location",
 		"visibility", "repo_admin_change_team_access",
 	); err != nil {
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index d65c8f667e886..d6b363558453e 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -107,7 +107,7 @@ func resetLocale(ctx *context.Context, u *user_model.User) error {
 	// If the user does not have a locale set, we save the current one.
 	if len(u.Language) == 0 {
 		u.Language = ctx.Locale.Language()
-		if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "language"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, u, "language"); err != nil {
 			return err
 		}
 	}
@@ -333,7 +333,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool, o
 	// If the user does not have a locale set, we save the current one.
 	if len(u.Language) == 0 {
 		u.Language = ctx.Locale.Language()
-		if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "language"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, u, "language"); err != nil {
 			ctx.ServerError("UpdateUserCols Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
 			return setting.AppSubURL + "/"
 		}
@@ -350,7 +350,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool, o
 
 	// Register last login
 	u.SetLastLogin()
-	if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "last_login_unix"); err != nil {
+	if err := user_model.UpdateUserCols(db.DefaultContext, u, "last_login_unix"); err != nil {
 		ctx.ServerError("UpdateUserCols", err)
 		return setting.AppSubURL + "/"
 	}
@@ -603,7 +603,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
 		u.IsAdmin = true
 		u.IsActive = true
 		u.SetLastLogin()
-		if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "is_admin", "is_active", "last_login_unix"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, u, "is_admin", "is_active", "last_login_unix"); err != nil {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
@@ -724,7 +724,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
 		ctx.ServerError("UpdateUser", err)
 		return
 	}
-	if err := user_model.UpdateUserCols(db.DefaultContext, user, false, "is_active", "rands"); err != nil {
+	if err := user_model.UpdateUserCols(db.DefaultContext, user, "is_active", "rands"); err != nil {
 		if user_model.IsErrUserNotExist(err) {
 			ctx.NotFound("UpdateUserCols", err)
 		} else {
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index ebcb0609e5942..fa2b0aa65f1f8 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -1021,7 +1021,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
 			cols = append(cols, "is_admin", "is_restricted")
 		}
 
-		if err := user_model.UpdateUserCols(db.DefaultContext, u, false, cols...); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, u, cols...); err != nil {
 			ctx.ServerError("UpdateUserCols", err)
 			return
 		}
@@ -1048,7 +1048,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
 
 	changed := setUserGroupClaims(source, u, &gothUser)
 	if changed {
-		if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "is_admin", "is_restricted"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, u, "is_admin", "is_restricted"); err != nil {
 			ctx.ServerError("UpdateUserCols", err)
 			return
 		}
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index 63c2572811eb8..65d5c55976eef 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -232,7 +232,7 @@ func ResetPasswdPost(ctx *context.Context) {
 		return
 	}
 	u.MustChangePassword = false
-	if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil {
+	if err := user_model.UpdateUserCols(db.DefaultContext, u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil {
 		ctx.ServerError("UpdateUser", err)
 		return
 	}
@@ -327,7 +327,7 @@ func MustChangePasswordPost(ctx *context.Context) {
 
 	u.MustChangePassword = false
 
-	if err := user_model.UpdateUserCols(db.DefaultContext, u, false, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
+	if err := user_model.UpdateUserCols(db.DefaultContext, u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
 		ctx.ServerError("UpdateUser", err)
 		return
 	}
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index ab3137f53d500..c523ef6703488 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -76,7 +76,7 @@ func AccountPost(ctx *context.Context) {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
-		if err := user_model.UpdateUserCols(db.DefaultContext, ctx.User, false, "salt", "passwd_hash_algo", "passwd"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, ctx.User, "salt", "passwd_hash_algo", "passwd"); err != nil {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 00c91ed6c46a0..084e905567dda 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -187,7 +187,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser *
 		}
 	}
 
-	if err := user_model.UpdateUserCols(db.DefaultContext, ctxUser, false, "avatar", "avatar_email", "use_custom_avatar"); err != nil {
+	if err := user_model.UpdateUserCols(db.DefaultContext, ctxUser, "avatar", "avatar_email", "use_custom_avatar"); err != nil {
 		return fmt.Errorf("UpdateUser: %v", err)
 	}
 
diff --git a/services/auth/auth.go b/services/auth/auth.go
index fd6239e8ef02c..3eb7f027d2e77 100644
--- a/services/auth/auth.go
+++ b/services/auth/auth.go
@@ -140,7 +140,7 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
 	if len(user.Language) == 0 {
 		lc := middleware.Locale(resp, req)
 		user.Language = lc.Language()
-		if err := user_model.UpdateUserCols(db.DefaultContext, user, false, "language"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, user, "language"); err != nil {
 			log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", user.ID, user.Language))
 			return
 		}
diff --git a/services/auth/source/db/authenticate.go b/services/auth/source/db/authenticate.go
index 99a52476b8d8c..f062f66ae039c 100644
--- a/services/auth/source/db/authenticate.go
+++ b/services/auth/source/db/authenticate.go
@@ -27,7 +27,7 @@ func Authenticate(user *user_model.User, login, password string) (*user_model.Us
 		if err := user.SetPassword(password); err != nil {
 			return nil, err
 		}
-		if err := user_model.UpdateUserCols(db.DefaultContext, user, false, "passwd", "passwd_hash_algo", "salt"); err != nil {
+		if err := user_model.UpdateUserCols(db.DefaultContext, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
 			return nil, err
 		}
 	}
diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index 9caf1c6c0331b..52971bb87e58c 100644
--- a/services/auth/source/ldap/source_authenticate.go
+++ b/services/auth/source/ldap/source_authenticate.go
@@ -50,7 +50,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str
 				cols = append(cols, "is_restricted")
 			}
 			if len(cols) > 0 {
-				err = user_model.UpdateUserCols(db.DefaultContext, user, false, cols...)
+				err = user_model.UpdateUserCols(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 44dc6a12f2238..d299e09cc2c34 100644
--- a/services/auth/source/ldap/source_sync.go
+++ b/services/auth/source/ldap/source_sync.go
@@ -154,7 +154,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
 				}
 				usr.IsActive = true
 
-				err = user_model.UpdateUserCols(db.DefaultContext, usr, true, "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")
 				if err != nil {
 					log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err)
 				}
@@ -195,7 +195,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, true, "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/user/user.go b/services/user/user.go
index 14dbf3383f58a..21f1a74f62f62 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -139,7 +139,7 @@ func UploadAvatar(u *user_model.User, data []byte) error {
 	// Otherwise, if any of the users delete his avatar
 	// Other users will lose their avatars too.
 	u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
-	if err = user_model.UpdateUserCols(ctx, u, false, "use_custom_avatar", "avatar"); err != nil {
+	if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil {
 		return fmt.Errorf("updateUser: %v", err)
 	}
 

From 25673a937efd78833756f298bfd344b214158151 Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Wed, 2 Feb 2022 11:42:22 +0100
Subject: [PATCH 08/11] Code cleanup

Related: https://github.com/go-gitea/gitea/pull/18466#pullrequestreview-869501363
Author-Change-Id: IB#1105051
---
 models/user/user.go         | 2 +-
 routers/web/admin/emails.go | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/models/user/user.go b/models/user/user.go
index 037584f1a7af9..d5f17857c1229 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -830,7 +830,7 @@ func validateUser(u *User) error {
 // 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 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)
diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go
index 73d01afc591c2..f3ac18e1834ee 100644
--- a/routers/web/admin/emails.go
+++ b/routers/web/admin/emails.go
@@ -118,7 +118,6 @@ 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"))

From 5c8f90510a5834de402e0251bbd8636c5a11fb56 Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Thu, 3 Feb 2022 10:15:19 +0100
Subject: [PATCH 09/11] User data must be updatable from LDAP on authentication

Fixes: cda4a8a21624587690a932206362d4f6a6d9bc30
Related: https://github.com/go-gitea/gitea/pull/18466
Author-Change-Id: IB#1105051
---
 services/auth/source/ldap/source_authenticate.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index 52971bb87e58c..2d8e19af2bd94 100644
--- a/services/auth/source/ldap/source_authenticate.go
+++ b/services/auth/source/ldap/source_authenticate.go
@@ -50,7 +50,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str
 				cols = append(cols, "is_restricted")
 			}
 			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
 				}

From e45a831bcd2263cae509f42b3dd66ca2b1307cdb Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Fri, 4 Feb 2022 11:09:48 +0100
Subject: [PATCH 10/11] Disable login prohibition and extra e-mail list

When local user management is disabled, active user login should
not be prohibited. Only primary user e-mail should be available
when local user management is enabled (only this mail is synchronized
from LDAP).

Related: https://github.com/go-gitea/gitea/pull/18466
Author-Change-Id: IB#1105051
---
 services/auth/source/ldap/source_authenticate.go | 8 +++++++-
 services/auth/source/ldap/source_sync.go         | 8 ++++++--
 templates/admin/user/edit.tmpl                   | 4 ++--
 templates/user/settings/account.tmpl             | 6 ++----
 4 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index 2d8e19af2bd94..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,6 +50,11 @@ 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.UpdateForceUserCols(db.DefaultContext, user, cols...)
 				if err != nil {
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go
index d299e09cc2c34..8ae81f2045459 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.UpdateForceUserCols(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)
 				}
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index 4f20dca8ecea1..04da6e1607dca 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -92,7 +92,7 @@
 
 				<div class="ui divider"></div>
 
-				<div class="inline field">
+				<div class="inline field" {{if DisableLocalUserManagement}} hidden{{end}}>
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.is_activated"}}</strong></label>
 						<input name="active" type="checkbox" {{if .User.IsActive}}checked{{end}} {{if DisableLocalUserManagement}}readonly{{end}}>
@@ -101,7 +101,7 @@
 				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.prohibit_login"}}</strong></label>
-						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if or (eq .User.ID .SignedUserID) DisableLocalUserManagement}}disabled{{end}}>
+						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if eq .User.ID .SignedUserID}}disabled{{end}}>
 					</div>
 				</div>
 				<div class="inline field">
diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl
index 198b1b327c9d2..ea2d7eac7c13e 100644
--- a/templates/user/settings/account.tmpl
+++ b/templates/user/settings/account.tmpl
@@ -72,8 +72,8 @@
 					</form>
 				</div>
 				{{range .Emails}}
+				{{if not DisableLocalUserManagement}}
 					<div class="item">
-{{if not DisableLocalUserManagement}}
 						{{if not .IsPrimary}}
 							<div class="right floated content">
 								<button class="ui red tiny button delete-button" data-modal-id="delete-email" data-url="{{AppSubUrl}}/user/settings/account/email/delete" data-id="{{.ID}}">
@@ -105,10 +105,8 @@
 								</form>
 							</div>
 						{{end}}
-{{end}}
 						<div class="content">
 							<strong>{{.Email}}</strong>
-{{if not DisableLocalUserManagement}}
 							{{if .IsPrimary}}
 								<div class="ui blue label">{{$.i18n.Tr "settings.primary"}}</div>
 							{{end}}
@@ -117,10 +115,10 @@
 							{{else}}
 								<div class="ui label">{{$.i18n.Tr "settings.requires_activation"}}</div>
 							{{end}}
-{{end}}
 						</div>
 					</div>
 				{{end}}
+				{{end}}
 			</div>
 		</div>
 {{if not DisableLocalUserManagement}}

From 38e5a4d6695aff84f6780f3e3bece625f19991d1 Mon Sep 17 00:00:00 2001
From: Pawel Boguslawski <pawel.boguslawski@ib.pl>
Date: Fri, 4 Feb 2022 11:25:32 +0100
Subject: [PATCH 11/11] Option hiding corrected

Fixes: e45a831bcd2263cae509f42b3dd66ca2b1307cdb
Related: https://github.com/go-gitea/gitea/pull/18466
Author-Change-Id: IB#1105051
---
 templates/admin/user/edit.tmpl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index 04da6e1607dca..9bcf668eabd0c 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -92,13 +92,13 @@
 
 				<div class="ui divider"></div>
 
-				<div class="inline field" {{if DisableLocalUserManagement}} hidden{{end}}>
+				<div class="inline field">
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.is_activated"}}</strong></label>
 						<input name="active" type="checkbox" {{if .User.IsActive}}checked{{end}} {{if DisableLocalUserManagement}}readonly{{end}}>
 					</div>
 				</div>
-				<div class="inline field">
+				<div class="inline field" {{if DisableLocalUserManagement}} hidden{{end}}>
 					<div class="ui checkbox">
 						<label><strong>{{.i18n.Tr "admin.users.prohibit_login"}}</strong></label>
 						<input name="prohibit_login" type="checkbox" {{if .User.ProhibitLogin}}checked{{end}} {{if eq .User.ID .SignedUserID}}disabled{{end}}>