From 24d7a86a8d35aa1fadf05deaa10e141d33ea6632 Mon Sep 17 00:00:00 2001
From: Girish Ramakrishnan <girish@cloudron.io>
Date: Tue, 18 Aug 2015 21:34:03 -0700
Subject: [PATCH] Set IsAdmin using LDAP

The IsAdmin flag is set based on whether the admin filter
returned any result. The admin filter is applied with the user dn
as the search root.

In the future, we should update IsAdmin as well on each login.
Alternately, we can have a periodic sync operation.
---
 conf/locale/locale_en-US.ini   |  1 +
 models/login.go                |  3 ++-
 modules/auth/auth_form.go      |  1 +
 modules/auth/ldap/ldap.go      | 31 ++++++++++++++++++++++++-------
 routers/admin/auths.go         |  2 ++
 templates/admin/auth/edit.tmpl |  4 ++++
 templates/admin/auth/new.tmpl  |  4 ++++
 7 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini
index 9fd7c4490d2..b2ccee5b504 100644
--- a/conf/locale/locale_en-US.ini
+++ b/conf/locale/locale_en-US.ini
@@ -761,6 +761,7 @@ auths.attribute_name = First name attribute
 auths.attribute_surname = Surname attribute
 auths.attribute_mail = E-mail attribute
 auths.filter = User Filter
+auths.admin_filter = Admin Filter
 auths.ms_ad_sa = Ms Ad SA
 auths.smtp_auth = SMTP Authorization Type
 auths.smtphost = SMTP Host
diff --git a/models/login.go b/models/login.go
index 8ac4b827efa..78a607263f9 100644
--- a/models/login.go
+++ b/models/login.go
@@ -257,7 +257,7 @@ func UserSignIn(uname, passwd string) (*User, error) {
 // Return the same LoginUserPlain semantic
 // FIXME: https://github.com/gogits/gogs/issues/672
 func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
-	fn, sn, mail, logged := cfg.Ldapsource.SearchEntry(name, passwd)
+	fn, sn, mail, admin, logged := cfg.Ldapsource.SearchEntry(name, passwd)
 	if !logged {
 		// User not in LDAP, do nothing
 		return nil, ErrUserNotExist{0, name}
@@ -281,6 +281,7 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
 		LoginName:   name,
 		Passwd:      passwd,
 		Email:       mail,
+		IsAdmin:     admin,
 		IsActive:    true,
 	}
 	return u, CreateUser(u)
diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go
index 94753b3eddc..bfae4b13c4d 100644
--- a/modules/auth/auth_form.go
+++ b/modules/auth/auth_form.go
@@ -23,6 +23,7 @@ type AuthenticationForm struct {
 	AttributeSurname  string
 	AttributeMail     string
 	Filter            string
+	AdminFilter       string
 	IsActived         bool
 	SMTPAuth          string `form:"smtp_auth"`
 	SMTPHost          string `form:"smtp_host"`
diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go
index 82d66fec374..7596762db5f 100644
--- a/modules/auth/ldap/ldap.go
+++ b/modules/auth/ldap/ldap.go
@@ -26,6 +26,7 @@ type Ldapsource struct {
 	AttributeSurname string // Surname attribute
 	AttributeMail    string // E-mail attribute
 	Filter           string // Query filter to validate entry
+	AdminFilter      string // Query filter to check if user is admin
 	Enabled          bool   // if this source is disabled
 }
 
@@ -77,17 +78,17 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
 }
 
 // searchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
-func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, bool) {
+func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, bool, bool) {
 	userDN, found := ls.FindUserDN(name)
 	if !found {
-		return "", "", "", false
+		return "", "", "", false, false
 	}
 
 	l, err := ldapDial(ls)
 	if err != nil {
 		log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
 		ls.Enabled = false
-		return "", "", "", false
+		return "", "", "", false, false
 	}
 
 	defer l.Close()
@@ -96,7 +97,7 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
 	err = l.Bind(userDN, passwd)
 	if err != nil {
 		log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err)
-		return "", "", "", false
+		return "", "", "", false, false
 	}
 
 	log.Trace("Bound successfully with userDN: %s", userDN)
@@ -109,16 +110,32 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
 	sr, err := l.Search(search)
 	if err != nil {
 		log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
-		return "", "", "", false
+		return "", "", "", false, false
 	} else if len(sr.Entries) < 1 {
 		log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
-		return "", "", "", false
+		return "", "", "", false, false
 	}
 
 	name_attr := sr.Entries[0].GetAttributeValue(ls.AttributeName)
 	sn_attr := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
 	mail_attr := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
-	return name_attr, sn_attr, mail_attr, true
+
+	search = ldap.NewSearchRequest(
+		userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ls.AdminFilter,
+		[]string{ls.AttributeName},
+		nil)
+
+	sr, err = l.Search(search)
+	admin_attr := false
+	if err != nil {
+		log.Error(4, "LDAP Admin Search failed unexpectedly! (%v)", err)
+	} else if len(sr.Entries) < 1 {
+		log.Error(4, "LDAP Admin Search failed")
+	} else {
+		admin_attr = true
+	}
+
+	return name_attr, sn_attr, mail_attr, admin_attr, true
 }
 
 func ldapDial(ls Ldapsource) (*ldap.Conn, error) {
diff --git a/routers/admin/auths.go b/routers/admin/auths.go
index bb73026b7df..8123eaaabca 100644
--- a/routers/admin/auths.go
+++ b/routers/admin/auths.go
@@ -71,6 +71,7 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 				BindPassword:      form.BindPassword,
 				UserBase:          form.UserBase,
 				Filter:            form.Filter,
+				AdminFilter:       form.AdminFilter,
 				AttributeName:     form.AttributeName,
 				AttributeSurname:  form.AttributeSurname,
 				AttributeMail:     form.AttributeMail,
@@ -160,6 +161,7 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
 				AttributeSurname:  form.AttributeSurname,
 				AttributeMail:     form.AttributeMail,
 				Filter:            form.Filter,
+				AdminFilter:       form.AdminFilter,
 				Enabled:           true,
 			},
 		}
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 3fd772de635..e99b163e66b 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -59,6 +59,10 @@
                                     <label class="req" for="filter">{{.i18n.Tr "admin.auths.filter"}}</label>
                                     <input class="ipt ipt-large ipt-radius {{if .Err_Filter}}ipt-error{{end}}" id="filter" name="filter" value="{{.Source.LDAP.Filter}}" />
                                 </div>
+                                <div class="field">
+                                    <label class="req" for="filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label>
+                                    <input class="ipt ipt-large ipt-radius {{if .Err_AdminFilter}}ipt-error{{end}}" id="admin_filter" name="admin_filter" value="{{.Source.LDAP.AdminFilter}}" />
+                                </div>
                                 <div class="field">
                                     <label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label>
                                     <input class="ipt ipt-large ipt-radius {{if .Err_Attributes}}ipt-error{{end}}" id="attribute_name" name="attribute_name" value="{{.Source.LDAP.AttributeName}}" />
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl
index 80d7c266f20..3f8f3d375b6 100644
--- a/templates/admin/auth/new.tmpl
+++ b/templates/admin/auth/new.tmpl
@@ -55,6 +55,10 @@
                                         <label class="req" for="filter">{{.i18n.Tr "admin.auths.filter"}}</label>
                                         <input class="ipt ipt-large ipt-radius {{if .Err_Filter}}ipt-error{{end}}" id="filter" name="filter" value="{{.filter}}" />
                                     </div>
+                                    <div class="field">
+                                        <label class="req" for="filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label>
+                                        <input class="ipt ipt-large ipt-radius {{if .Err_AdminFilter}}ipt-error{{end}}" id="admin_filter" name="admin_filter" value="{{.admin_filter}}" />
+                                    </div>
                                     <div class="field">
                                         <label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label>
                                         <input class="ipt ipt-large ipt-radius {{if .Err_AttributeName}}ipt-error{{end}}" id="attribute_name" name="attribute_name" value="{{.attribute_name}}" />