From 7d84d4a8f0cb30df04241f528ed74672a485274a Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 12 Aug 2015 16:58:27 -0700 Subject: Significantly enhanced LDAP support in Gogs. --- modules/auth/auth_form.go | 9 ++-- modules/auth/ldap/README.md | 79 +++++++++++++++++---------- modules/auth/ldap/ldap.go | 118 ++++++++++++++++++++++++++--------------- modules/auth/ldap/ldap_test.go | 29 ---------- 4 files changed, 129 insertions(+), 106 deletions(-) delete mode 100644 modules/auth/ldap/ldap_test.go (limited to 'modules/auth') diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 1102dc3492..94753b3edd 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -13,17 +13,16 @@ type AuthenticationForm struct { ID int64 `form:"id"` Type int Name string `binding:"Required;MaxSize(50)"` - Domain string Host string Port int - UseSSL bool `form:"usessl"` - BaseDN string `form:"base_dn"` - AttributeUsername string + UseSSL bool `form:"use_ssl"` + BindDN string `form:"bind_dn"` + BindPassword string + UserBase string AttributeName string AttributeSurname string AttributeMail string Filter string - MsAdSA string `form:"ms_ad_sa"` IsActived bool SMTPAuth string `form:"smtp_auth"` SMTPHost string `form:"smtp_host"` diff --git a/modules/auth/ldap/README.md b/modules/auth/ldap/README.md index 531ba85361..5d515848e2 100644 --- a/modules/auth/ldap/README.md +++ b/modules/auth/ldap/README.md @@ -1,43 +1,64 @@ -LDAP authentication -=================== +Gogs LDAP Authentication Module +=============================== -## Goal +## About -Authenticat user against LDAP directories +This authentication module attempts to authorize and authenticate a user +against an LDAP server. Like most LDAP authentication systems, this module does +this in two steps. First, it queries the LDAP server using a Bind DN and +searches for the user that is attempting to sign in. If the user is found, the +module attempts to bind to the server using the user's supplied credentials. If +this succeeds, the user has been authenticated, and his account information is +retrieved and passed to the Gogs login infrastructure. -It will bind with the user's login/pasword and query attributs ("mail" for instance) in a pool of directory servers +## Usage -The first OK wins. +To use this module, add an LDAP authentication source via the Authentications +section in the admin panel. The fields should be set as follows: -If there's connection error, the server will be disabled and won't be checked again +Authorization Name (required) + A name to assign to the new method of authorization. -## Usage +Host (required) + The address where the LDAP server can be reached. + Example: mydomain.com + +Port (required) + The port to use when connecting to the server. + Example: 636 -In the [security] section, set -> LDAP_AUTH = true +Enable TLS Encryption (optional) + Whether to use TLS when connecting to the LDAP server. -then for each LDAP source, set +Bind DN (optional) + The DN to bind to the LDAP server with when searching for the user. + This may be left blank to perform an anonymous search. + Example: cn=Search,dc=mydomain,dc=com -> [LdapSource-someuniquename] -> name=canonicalName -> host=hostname-or-ip -> port=3268 # or regular LDAP port -> # the following settings depend highly how you've configured your AD -> basedn=dc=ACME,dc=COM -> MSADSAFORMAT=%s@ACME.COM -> filter=(&(objectClass=user)(sAMAccountName=%s)) +Bind Password (optional) + The password for the Bind DN specified above, if any. -### Limitation +User Search Base (required) + The LDAP base at which user accounts will be searched for. + Example: ou=Users,dc=mydomain,dc=com -Only tested on an MS 2008R2 DC, using global catalog (TCP/3268) +User Filter (required) + An LDAP filter declaring how to find the user record that is attempting + to authenticate. The '%s' matching parameter will be substituted with + the user's username. + Example: (&(objectClass=posixAccount)(uid=%s)) -This MSAD is a mess. +First name attribute (optional) + The attribute of the user's LDAP record containing the user's first + name. This will be used to populate their account information. + Example: givenName -The way how one checks the directory (CN, DN etc...) may be highly depending local custom configuration +Surname name attribute (optional) + The attribute of the user's LDAP record containing the user's surname + This will be used to populate their account information. + Example: sn -### Todo -* Define a timeout per server -* Check servers marked as "Disabled" when they'll come back online -* Find a more flexible way to define filter/MSADSAFORMAT/Attributes etc... maybe text/template ? -* Check OpenLDAP server -* SSL support ? \ No newline at end of file +E-mail attribute (required) + The attribute of the user's LDAP record containing the user's email + address. This will be used to populate their account information. + Example: mail diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index c78e241d37..f75d906e1d 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -19,82 +19,114 @@ type Ldapsource struct { Host string // LDAP host Port int // port number UseSSL bool // Use SSL - BaseDN string // Base DN - AttributeUsername string // Username attribute + BindDN string // DN to bind with + BindPassword string // Bind DN password + UserBase string // Base search path for users AttributeName string // First name attribute AttributeSurname string // Surname attribute AttributeMail string // E-mail attribute Filter string // Query filter to validate entry - MsAdSAFormat string // in the case of MS AD Simple Authen, the format to use (see: http://msdn.microsoft.com/en-us/library/cc223499.aspx) Enabled bool // if this source is disabled } -//Global LDAP directory pool -var ( - Authensource []Ldapsource -) - -// Add a new source (LDAP directory) to the global pool -func AddSource(name string, host string, port int, usessl bool, basedn string, attribcn string, attribname string, attribsn string, attribmail string, filter string, msadsaformat string) { - ldaphost := Ldapsource{name, host, port, usessl, basedn, attribcn, attribname, attribsn, attribmail, filter, msadsaformat, true} - Authensource = append(Authensource, ldaphost) -} +func (ls Ldapsource) FindUserDN(name string) (userDN string, success bool) { + userDN = "" + success = false + l, err := ldapDial(ls) + if err != nil { + log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err) + ls.Enabled = false + return + } -//LoginUser : try to login an user to LDAP sources, return requested (attribute,true) if ok, ("",false) other wise -//First match wins -//Returns first attribute if exists -func LoginUser(name, passwd string) (cn, fn, sn, mail string, r bool) { - r = false - for _, ls := range Authensource { - cn, fn, sn, mail, r = ls.SearchEntry(name, passwd) - if r { + defer l.Close() + log.Trace("Search for LDAP user: %s", name) + if ls.BindDN != "" && ls.BindPassword != "" { + err = l.Bind(ls.BindDN, ls.BindPassword) + if err != nil { + log.Debug("Failed to bind as BindDN: %s, %s", ls.BindDN, err.Error()) return } + log.Trace("Bound as BindDN %s", ls.BindDN) + } else { + log.Trace("Proceeding with anonymous LDAP search.") + } + + // A search for the user. + userFilter := fmt.Sprintf(ls.Filter, name) + log.Trace("Searching using filter %s", userFilter) + search := ldap.NewSearchRequest( + ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, + false, userFilter, []string{}, nil) + + // Ensure we found a user + sr, err := l.Search(search) + if err != nil || len(sr.Entries) < 1 { + log.Debug("Failed search using filter %s: %s", userFilter, err.Error()) + return + } else if len(sr.Entries) > 1 { + log.Debug("Filter '%s' returned more than one user.", userFilter) + return } + + userDN = sr.Entries[0].DN + if userDN == "" { + log.Error(4, "LDAP search was succesful, but found no DN!") + return + } + + success = true return } -// searchEntry : search an LDAP source if an entry (name, passwd) is valide and in the specific filter -func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, 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) { + userDN, found := ls.FindUserDN(name) + if !found { + return "", "", "", 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 } + defer l.Close() - nx := fmt.Sprintf(ls.MsAdSAFormat, name) - err = l.Bind(nx, passwd) + log.Trace("Binding with userDN: %s", userDN) + err = l.Bind(userDN, passwd) if err != nil { - log.Debug("LDAP Authan failed for %s, reason: %s", nx, err.Error()) - return "", "", "", "", false + log.Debug("LDAP auth. failed for %s, reason: %s", userDN, err.Error()) + return "", "", "", false } + log.Trace("Bound successfully with userDN: %s", userDN) + userFilter := fmt.Sprintf(ls.Filter, name) search := ldap.NewSearchRequest( - ls.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf(ls.Filter, name), - []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, + userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, + []string{ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, nil) + sr, err := l.Search(search) if err != nil { - log.Debug("LDAP Authen OK but not in filter %s", name) - return "", "", "", "", false - } - log.Debug("LDAP Authen OK: %s", name) - if len(sr.Entries) > 0 { - cn := sr.Entries[0].GetAttributeValue(ls.AttributeUsername) - name := sr.Entries[0].GetAttributeValue(ls.AttributeName) - sn := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) - mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail) - return cn, name, sn, mail, true + log.Error(4, "LDAP Search failed unexpectedly! (%s)", err.Error()) + return "", "", "", false + } else if len(sr.Entries) < 1 { + log.Error(4, "LDAP Search failed unexpectedly! (0 entries)") + return "", "", "", false } - return "", "", "", "", true + + 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 } func ldapDial(ls Ldapsource) (*ldap.Conn, error) { if ls.UseSSL { + log.Debug("Using TLS for LDAP") return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil) } else { return ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port)) diff --git a/modules/auth/ldap/ldap_test.go b/modules/auth/ldap/ldap_test.go deleted file mode 100644 index a842eebf9b..0000000000 --- a/modules/auth/ldap/ldap_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package ldap - -// import ( -// "fmt" -// "testing" -// ) - -// var ldapServer = "ldap.itd.umich.edu" -// var ldapPort = 389 -// var baseDN = "dc=umich,dc=edu" -// var filter = []string{ -// "(cn=cis-fac)", -// "(&(objectclass=rfc822mailgroup)(cn=*Computer*))", -// "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"} -// var attributes = []string{ -// "cn", -// "description"} -// var msadsaformat = "" - -// func TestLDAP(t *testing.T) { -// AddSource("test", ldapServer, ldapPort, baseDN, attributes, filter, msadsaformat) -// user, err := LoginUserLdap("xiaolunwen", "") -// if err != nil { -// t.Error(err) -// return -// } - -// fmt.Println(user) -// } -- cgit v1.2.3 From f8a4ab25fda9731803e787160f54d767d1b57eef Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 12 Aug 2015 17:08:16 -0700 Subject: Updated the LDAP module readme. --- modules/auth/ldap/README.md | 58 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) (limited to 'modules/auth') diff --git a/modules/auth/ldap/README.md b/modules/auth/ldap/README.md index 5d515848e2..208f148e2f 100644 --- a/modules/auth/ldap/README.md +++ b/modules/auth/ldap/README.md @@ -16,49 +16,49 @@ retrieved and passed to the Gogs login infrastructure. To use this module, add an LDAP authentication source via the Authentications section in the admin panel. The fields should be set as follows: -Authorization Name (required) - A name to assign to the new method of authorization. +* Authorization Name **(required)** + * A name to assign to the new method of authorization. -Host (required) - The address where the LDAP server can be reached. - Example: mydomain.com +* Host **(required)** + * The address where the LDAP server can be reached. + * Example: mydomain.com -Port (required) - The port to use when connecting to the server. - Example: 636 +* Port **(required)** + * The port to use when connecting to the server. + * Example: 636 -Enable TLS Encryption (optional) - Whether to use TLS when connecting to the LDAP server. +* Enable TLS Encryption (optional) + * Whether to use TLS when connecting to the LDAP server. -Bind DN (optional) - The DN to bind to the LDAP server with when searching for the user. +* Bind DN (optional) + * The DN to bind to the LDAP server with when searching for the user. This may be left blank to perform an anonymous search. - Example: cn=Search,dc=mydomain,dc=com + * Example: cn=Search,dc=mydomain,dc=com -Bind Password (optional) - The password for the Bind DN specified above, if any. +* Bind Password (optional) + * The password for the Bind DN specified above, if any. -User Search Base (required) - The LDAP base at which user accounts will be searched for. - Example: ou=Users,dc=mydomain,dc=com +* User Search Base **(required)** + * The LDAP base at which user accounts will be searched for. + * Example: ou=Users,dc=mydomain,dc=com -User Filter (required) - An LDAP filter declaring how to find the user record that is attempting +* User Filter **(required)** + * An LDAP filter declaring how to find the user record that is attempting to authenticate. The '%s' matching parameter will be substituted with the user's username. - Example: (&(objectClass=posixAccount)(uid=%s)) + * Example: (&(objectClass=posixAccount)(uid=%s)) -First name attribute (optional) - The attribute of the user's LDAP record containing the user's first +* First name attribute (optional) + * The attribute of the user's LDAP record containing the user's first name. This will be used to populate their account information. - Example: givenName + * Example: givenName -Surname name attribute (optional) - The attribute of the user's LDAP record containing the user's surname +* Surname name attribute (optional) + *The attribute of the user's LDAP record containing the user's surname This will be used to populate their account information. - Example: sn + * Example: sn -E-mail attribute (required) +* E-mail attribute (required) The attribute of the user's LDAP record containing the user's email address. This will be used to populate their account information. - Example: mail + * Example: mail -- cgit v1.2.3