From 68bca621cd0e9e2ebcb1392e7aab62d79dc4df55 Mon Sep 17 00:00:00 2001
From: dioss-Machiel <machiel.sleeuwaert@dioss.com>
Date: Mon, 20 Jan 2020 21:02:35 +0100
Subject: [PATCH] Prevent empty LDAP search from deactivating all users (#9879)
 (#9890)

* Backport of #9879 (Add option to prevent LDAP from deactivating everything on empty search)

* go fmtted

Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com>
Co-authored-by: zeripath <art27@cantab.net>
---
 cmd/admin_auth_ldap.go          | 7 +++++++
 models/user.go                  | 9 +++++++++
 modules/auth/auth_form.go       | 1 +
 modules/auth/ldap/ldap.go       | 1 +
 options/locale/locale_en-US.ini | 1 +
 routers/admin/auths.go          | 1 +
 templates/admin/auth/edit.tmpl  | 6 ++++++
 7 files changed, 26 insertions(+)

diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go
index cce3aa894f..e869686cbd 100644
--- a/cmd/admin_auth_ldap.go
+++ b/cmd/admin_auth_ldap.go
@@ -61,6 +61,10 @@ var (
 			Name:  "admin-filter",
 			Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
 		},
+		cli.BoolFlag{
+			Name:  "allow-deactivate-all",
+			Usage: "Allow empty search results to deactivate all users.",
+		},
 		cli.StringFlag{
 			Name:  "username-attribute",
 			Usage: "The attribute of the user’s LDAP record containing the user name.",
@@ -231,6 +235,9 @@ func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
 	if c.IsSet("admin-filter") {
 		config.Source.AdminFilter = c.String("admin-filter")
 	}
+	if c.IsSet("allow-deactivate-all") {
+		config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
+	}
 	return nil
 }
 
diff --git a/models/user.go b/models/user.go
index 1a466313c5..64b832a7b6 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1715,6 +1715,15 @@ func SyncExternalUsers() {
 				continue
 			}
 
+			if len(sr) == 0 {
+				if !s.LDAP().AllowDeactivateAll {
+					log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
+					continue
+				} else {
+					log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
+				}
+			}
+
 			for _, su := range sr {
 				if len(su.Username) == 0 {
 					continue
diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go
index 358472a385..6a1cebca85 100644
--- a/modules/auth/auth_form.go
+++ b/modules/auth/auth_form.go
@@ -30,6 +30,7 @@ type AuthenticationForm struct {
 	SearchPageSize                int
 	Filter                        string
 	AdminFilter                   string
+	AllowDeactivateAll            bool
 	IsActive                      bool
 	IsSyncEnabled                 bool
 	SMTPAuth                      string
diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go
index ed83a77e12..7f0d2c93f3 100644
--- a/modules/auth/ldap/ldap.go
+++ b/modules/auth/ldap/ldap.go
@@ -47,6 +47,7 @@ type Source struct {
 	Filter                string // Query filter to validate entry
 	AdminFilter           string // Query filter to check if user is admin
 	Enabled               bool   // if this source is disabled
+	AllowDeactivateAll    bool   // Allow an empty search response to deactivate all users from this source
 }
 
 // SearchResult : user data
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 94ded93c59..d7c9930215 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1700,6 +1700,7 @@ auths.attribute_surname = Surname Attribute
 auths.attribute_mail = Email Attribute
 auths.attribute_ssh_public_key = Public SSH Key Attribute
 auths.attributes_in_bind = Fetch Attributes in Bind DN Context
+auths.allow_deactivate_all = Allow an empty search result to deactivate all users
 auths.use_paged_search = Use Paged Search
 auths.search_page_size = Page Size
 auths.filter = User Filter
diff --git a/routers/admin/auths.go b/routers/admin/auths.go
index 8e0c27e226..922f35d2ee 100644
--- a/routers/admin/auths.go
+++ b/routers/admin/auths.go
@@ -115,6 +115,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig {
 			SearchPageSize:        pageSize,
 			Filter:                form.Filter,
 			AdminFilter:           form.AdminFilter,
+			AllowDeactivateAll:    form.AllowDeactivateAll,
 			Enabled:               true,
 		},
 	}
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 6a176a43a8..ec5af3d4f0 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -112,6 +112,12 @@
 							</div>
 						</div>
 					{{end}}
+					<div class="inline field">
+						<div class="ui checkbox">
+							<label for="allow_deactivate_all"><strong>{{.i18n.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
+							<input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if $cfg.AllowDeactivateAll}}checked{{end}}>
+						</div>
+					</div>
 				{{end}}
 
 				<!-- SMTP -->