diff --git a/domain/auth/ldap/ad_test.go b/domain/auth/ldap/ad_test.go index 10109961..401b7219 100644 --- a/domain/auth/ldap/ad_test.go +++ b/domain/auth/ldap/ad_test.go @@ -12,7 +12,6 @@ package ldap import ( - "crypto/tls" "fmt" "strings" "testing" @@ -23,65 +22,54 @@ import ( // Works against AD server in Azure confgiured using: // -// https://auth0.com/docs/connector/test-dc +// https://auth0.com/docs/connector/test-dc // // Ensure VM network settings open up ports 389 and 636. +var testConfigPublicAD = lm.LDAPConfig{ + ServerType: lm.ServerTypeAD, + ServerHost: "documize-ad.eastus.cloudapp.azure.com", + ServerPort: 389, + EncryptionType: "none", + BaseDN: "DC=mycompany,DC=local", + BindDN: "CN=ad-admin,CN=Users,DC=mycompany,DC=local", + BindPassword: "8B5tNRLvbk8K", + UserFilter: "", + GroupFilter: "", + AttributeUserRDN: "sAMAccountName", + AttributeUserFirstname: "givenName", + AttributeUserLastname: "sn", + AttributeUserEmail: "mail", + AttributeUserDisplayName: "", + AttributeUserGroupName: "", + AttributeGroupMember: "member", +} + func TestADServer_UserList(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "40.117.188.17" - c.ServerPort = 389 - c.EncryptionType = "none" - c.BaseDN = "DC=mycompany,DC=local" - c.BindDN = "CN=ad-admin,CN=Users,DC=mycompany,DC=local" - c.BindPassword = "8B5tNRLvbk8K" - c.UserFilter = "" - c.GroupFilter = "" + testConfigPublicAD.UserFilter = "(|(objectCategory=person)(objectClass=user)(objectClass=inetOrgPerson))" + testConfigPublicAD.GroupFilter = "" + userAttrs := testConfigPublicAD.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - - t.Log("Connecting to AD server", address) - - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigPublicAD) if err != nil { - t.Error("Error: unable to dial AD server: ", err.Error()) + t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with AD server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with AD server: ", err.Error()) - return - } - } - // Authenticate with AD server using admin credentials. - t.Log("Binding AD admin user") - err = l.Bind(c.BindDN, c.BindPassword) + t.Log("Binding LDAP admin user") + err = l.Bind(testConfigPublicAD.BindDN, testConfigPublicAD.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to AD: ", err.Error()) return } - // Get users from AD server by using filter - filter := "" - attrs := []string{} - if len(c.GroupFilter) > 0 { - filter = fmt.Sprintf("(&(objectClass=group)(cn=%s))", c.GroupFilter) - attrs = []string{"cn"} - } else { - filter = "(|(objectCategory=person)(objectClass=user)(objectClass=inetOrgPerson))" - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "sAMAccountName"} - } - searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicAD.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - attrs, + testConfigPublicAD.UserFilter, + userAttrs, nil, ) @@ -99,74 +87,44 @@ func TestADServer_UserList(t *testing.T) { for _, entry := range sr.Entries { t.Logf("[%s] %s (%s %s) @ %s\n", - entry.GetAttributeValue("sAMAccountName"), + entry.GetAttributeValue(testConfigPublicAD.AttributeUserRDN), entry.GetAttributeValue("cn"), - entry.GetAttributeValue("givenName"), - entry.GetAttributeValue("sn"), - entry.GetAttributeValue("mail")) + entry.GetAttributeValue(testConfigPublicAD.AttributeUserFirstname), + entry.GetAttributeValue(testConfigPublicAD.AttributeUserLastname), + entry.GetAttributeValue(testConfigPublicAD.AttributeUserEmail)) } } func TestADServer_Groups(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "40.117.188.17" - c.ServerPort = 389 - c.EncryptionType = "none" - c.BaseDN = "DC=mycompany,DC=local" - c.BindDN = "CN=ad-admin,CN=Users,DC=mycompany,DC=local" - c.BindPassword = "8B5tNRLvbk8K" - c.UserFilter = "" - c.GroupFilter = "(|(cn=Accounting)(cn=IT))" + testConfigPublicAD.UserFilter = "" + testConfigPublicAD.GroupFilter = "(|(cn=Accounting)(cn=IT))" + groupAttrs := testConfigPublicAD.GetGroupFilterAttributes() + userAttrs := testConfigPublicAD.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - t.Log("Connecting to AD server", address) - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigPublicAD) if err != nil { t.Error("Error: unable to dial AD server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with AD server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with AD server: ", err.Error()) - return - } - } - - // Authenticate with AD server using admin credentials. - t.Log("Binding AD admin user") - err = l.Bind(c.BindDN, c.BindPassword) + // Authenticate with LDAP server using admin credentials. + t.Log("Binding LDAP admin user") + err = l.Bind(testConfigPublicAD.BindDN, testConfigPublicAD.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to AD: ", err.Error()) return } - // Get users from AD server by using filter - filter := "" - attrs := []string{} - if len(c.GroupFilter) > 0 { - filter = c.GroupFilter - attrs = []string{"dn", "cn", "member"} - } else if len(c.UserFilter) > 0 { - filter = c.UserFilter - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "sAMAccountName"} - } else { - filter = "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "sAMAccountName"} - } - searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicAD.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - attrs, + testConfigPublicAD.GroupFilter, + groupAttrs, nil, ) - t.Log("AD search filter:", filter) + t.Log("AD search filter:", testConfigPublicAD.GroupFilter) sr, err := l.Search(searchRequest) if err != nil { t.Error("Error: unable to execute directory search: ", err.Error()) @@ -181,7 +139,9 @@ func TestADServer_Groups(t *testing.T) { // Get list of group members for each group found. for _, group := range sr.Entries { - rawMembers := group.GetAttributeValues("member") + t.Log("Found group", group.DN) + + rawMembers := group.GetAttributeValues(testConfigPublicAD.AttributeGroupMember) fmt.Printf("%s", group.DN) if len(rawMembers) == 0 { @@ -200,10 +160,10 @@ func TestADServer_Groups(t *testing.T) { filter := fmt.Sprintf("(%s)", parts[0]) usr := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicAD.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, filter, - []string{"dn", "cn", "givenName", "sn", "mail", "sAMAccountName"}, + userAttrs, nil, ) ue, err := l.Search(usr) @@ -215,11 +175,11 @@ func TestADServer_Groups(t *testing.T) { if len(ue.Entries) > 0 { for _, ur := range ue.Entries { t.Logf("[%s] %s (%s %s) @ %s\n", - ur.GetAttributeValue("sAMAccountName"), + ur.GetAttributeValue(testConfigPublicAD.AttributeUserRDN), ur.GetAttributeValue("cn"), - ur.GetAttributeValue("givenName"), - ur.GetAttributeValue("sn"), - ur.GetAttributeValue("mail")) + ur.GetAttributeValue(testConfigPublicAD.AttributeUserFirstname), + ur.GetAttributeValue(testConfigPublicAD.AttributeUserLastname), + ur.GetAttributeValue(testConfigPublicAD.AttributeUserEmail)) } } else { t.Log("group member search failed:", filter) @@ -227,53 +187,34 @@ func TestADServer_Groups(t *testing.T) { } } } - func TestADServer_Authenticate(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "40.117.188.17" - c.ServerPort = 389 - c.EncryptionType = "none" - c.BaseDN = "DC=mycompany,DC=local" - c.BindDN = "CN=ad-admin,CN=Users,DC=mycompany,DC=local" - c.BindPassword = "8B5tNRLvbk8K" - c.UserFilter = "" - c.GroupFilter = "" + testConfigPublicAD.UserFilter = "" + testConfigPublicAD.GroupFilter = "" + userAttrs := testConfigPublicAD.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - t.Log("Connecting to AD server", address) - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigPublicAD) if err != nil { - t.Error("Error: unable to dial AD server: ", err.Error()) + t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with AD server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with AD server: ", err.Error()) - return - } - } - // Authenticate with AD server using admin credentials. t.Log("Binding AD admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigPublicAD.BindDN, testConfigPublicAD.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to AD: ", err.Error()) return } - username := `bob.johnson` password := "Pass@word1!" - filter := fmt.Sprintf("(sAMAccountName=%s)", username) + filter := fmt.Sprintf("(%s=%s)", testConfigPublicAD.AttributeUserRDN, username) searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicAD.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, filter, - []string{"mail"}, + userAttrs, nil, ) diff --git a/domain/auth/ldap/ldap.go b/domain/auth/ldap/ldap.go new file mode 100644 index 00000000..fe9211ec --- /dev/null +++ b/domain/auth/ldap/ldap.go @@ -0,0 +1,45 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package ldap + +import ( + "crypto/tls" + "fmt" + + lm "github.com/documize/community/model/auth" + "github.com/pkg/errors" + ld "gopkg.in/ldap.v2" +) + +// Connect establishes connection to LDAP server. +func Connect(c lm.LDAPConfig) (l *ld.Conn, err error) { + address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) + + fmt.Println("Connecting to LDAP server", address) + + l, err = ld.Dial("tcp", address) + if err != nil { + err = errors.Wrap(err, "unable to dial LDAP server") + return + } + + if c.EncryptionType == "starttls" { + fmt.Println("Using StartTLS with LDAP server") + err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + err = errors.Wrap(err, "unable to startTLS with LDAP server") + return + } + } + + return +} diff --git a/domain/auth/ldap/local_test.go b/domain/auth/ldap/local_test.go index 9c63d106..af4fcbed 100644 --- a/domain/auth/ldap/local_test.go +++ b/domain/auth/ldap/local_test.go @@ -12,7 +12,6 @@ package ldap import ( - "crypto/tls" "fmt" "strings" "testing" @@ -24,68 +23,54 @@ import ( // Works against https://github.com/rroemhild/docker-test-openldap // Use docker run --privileged -d -p 389:389 rroemhild/test-openldap +var testConfigLocalLDAP = lm.LDAPConfig{ + ServerType: lm.ServerTypeLDAP, + ServerHost: "127.0.0.1", + ServerPort: 389, + EncryptionType: "starttls", + BaseDN: "ou=people,dc=planetexpress,dc=com", + BindDN: "cn=admin,dc=planetexpress,dc=com", + BindPassword: "GoodNewsEveryone", + UserFilter: "", + GroupFilter: "", + AttributeUserRDN: "uid", + AttributeUserFirstname: "givenName", + AttributeUserLastname: "sn", + AttributeUserEmail: "mail", + AttributeUserDisplayName: "", + AttributeUserGroupName: "", + AttributeGroupMember: "member", +} + func TestLocalLDAPServer_AllUsers(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "127.0.0.1" - c.ServerPort = 389 - c.EncryptionType = "starttls" - c.BaseDN = "ou=people,dc=planetexpress,dc=com" - c.BindDN = "cn=admin,dc=planetexpress,dc=com" - c.BindPassword = "GoodNewsEveryone" - c.UserFilter = "" - c.GroupFilter = "" + testConfigLocalLDAP.UserFilter = "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + testConfigLocalLDAP.GroupFilter = "" + userAttrs := testConfigLocalLDAP.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - - t.Log("Connecting to LDAP server", address) - - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigLocalLDAP) if err != nil { t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with LDAP server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with LDAP server: ", err.Error()) - return - } - } - // Authenticate with LDAP server using admin credentials. t.Log("Binding LDAP admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigLocalLDAP.BindDN, testConfigLocalLDAP.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) return } - // Get users from LDAP server by using filter - filter := "" - attrs := []string{} - if len(c.GroupFilter) > 0 { - filter = c.GroupFilter - attrs = []string{"dn", "cn"} - } else if len(c.UserFilter) > 0 { - filter = c.UserFilter - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } else { - filter = "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } - searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigLocalLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - attrs, + testConfigLocalLDAP.UserFilter, + userAttrs, nil, ) - t.Log("LDAP search filter:", filter) + t.Log("LDAP search filter:", testConfigLocalLDAP.UserFilter) sr, err := l.Search(searchRequest) if err != nil { t.Error("Error: unable to execute directory search: ", err.Error()) @@ -100,76 +85,44 @@ func TestLocalLDAPServer_AllUsers(t *testing.T) { for _, entry := range sr.Entries { t.Logf("[%s] %s (%s %s) @ %s\n", - entry.GetAttributeValue("uid"), + entry.GetAttributeValue(testConfigLocalLDAP.AttributeUserRDN), entry.GetAttributeValue("cn"), - entry.GetAttributeValue("givenName"), - entry.GetAttributeValue("sn"), - entry.GetAttributeValue("mail")) + entry.GetAttributeValue(testConfigLocalLDAP.AttributeUserFirstname), + entry.GetAttributeValue(testConfigLocalLDAP.AttributeUserLastname), + entry.GetAttributeValue(testConfigLocalLDAP.AttributeUserEmail)) } } func TestLocalLDAPServer_UsersInGroup(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "127.0.0.1" - c.ServerPort = 389 - c.EncryptionType = "starttls" - c.BaseDN = "dc=planetexpress,dc=com" - c.BindDN = "cn=admin,dc=planetexpress,dc=com" - c.BindPassword = "GoodNewsEveryone" - c.UserFilter = "" - c.GroupFilter = "(&(objectClass=group)(|(cn=ship_crew)(cn=admin_staff)))" + testConfigLocalLDAP.UserFilter = "" + testConfigLocalLDAP.GroupFilter = "(&(objectClass=group)(|(cn=ship_crew)(cn=admin_staff)))" + groupAttrs := testConfigLocalLDAP.GetGroupFilterAttributes() + userAttrs := testConfigLocalLDAP.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - - t.Log("Connecting to LDAP server", address) - - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigLocalLDAP) if err != nil { t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with LDAP server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with LDAP server: ", err.Error()) - return - } - } - // Authenticate with LDAP server using admin credentials. t.Log("Binding LDAP admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigLocalLDAP.BindDN, testConfigLocalLDAP.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) return } - // Get users from LDAP server by using filter - filter := "" - attrs := []string{} - if len(c.GroupFilter) > 0 { - filter = c.GroupFilter - attrs = []string{"cn", "member"} - } else if len(c.UserFilter) > 0 { - filter = c.UserFilter - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } else { - filter = "(|(objectCategory=person)(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } - searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigLocalLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - attrs, + testConfigLocalLDAP.GroupFilter, + groupAttrs, nil, ) - t.Log("LDAP search filter:", filter) + t.Log("LDAP search filter:", testConfigLocalLDAP.GroupFilter) sr, err := l.Search(searchRequest) if err != nil { t.Error("Error: unable to execute directory search: ", err.Error()) @@ -184,7 +137,9 @@ func TestLocalLDAPServer_UsersInGroup(t *testing.T) { // Get list of group members per group found. for _, group := range sr.Entries { - rawMembers := group.GetAttributeValues("member") + t.Log("Found group", group.DN) + + rawMembers := group.GetAttributeValues(testConfigLocalLDAP.AttributeGroupMember) if len(rawMembers) == 0 { t.Log("Error: group member attribute returned no users") continue @@ -201,10 +156,10 @@ func TestLocalLDAPServer_UsersInGroup(t *testing.T) { filter := fmt.Sprintf("(%s)", parts[0]) usr := ld.NewSearchRequest( - c.BaseDN, + testConfigLocalLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, filter, - []string{"dn", "cn", "givenName", "sn", "mail", "uid"}, + userAttrs, nil, ) ue, err := l.Search(usr) @@ -215,7 +170,12 @@ func TestLocalLDAPServer_UsersInGroup(t *testing.T) { if len(ue.Entries) > 0 { for _, ur := range ue.Entries { - t.Logf("%s", ur.GetAttributeValue("mail")) + t.Logf("[%s] %s (%s %s) @ %s\n", + ur.GetAttributeValue(testConfigLocalLDAP.AttributeUserRDN), + ur.GetAttributeValue("cn"), + ur.GetAttributeValue(testConfigLocalLDAP.AttributeUserFirstname), + ur.GetAttributeValue(testConfigLocalLDAP.AttributeUserLastname), + ur.GetAttributeValue(testConfigLocalLDAP.AttributeUserEmail)) } } else { t.Log("group member search failed:", filter) @@ -224,37 +184,20 @@ func TestLocalLDAPServer_UsersInGroup(t *testing.T) { } } func TestLocalLDAP_Authenticate(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "127.0.0.1" - c.ServerPort = 389 - c.EncryptionType = "starttls" - c.BaseDN = "ou=people,dc=planetexpress,dc=com" - c.BindDN = "cn=admin,dc=planetexpress,dc=com" - c.BindPassword = "GoodNewsEveryone" - c.UserFilter = "" - c.GroupFilter = "" + testConfigLocalLDAP.UserFilter = "" + testConfigLocalLDAP.GroupFilter = "" + userAttrs := testConfigLocalLDAP.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - t.Log("Connecting to LDAP server", address) - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigLocalLDAP) if err != nil { - t.Error("Error: unable to dial AD server: ", err.Error()) + t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with LDAP server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with LDAP server: ", err.Error()) - return - } - } - // Authenticate with LDAP server using admin credentials. t.Log("Binding LDAP admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigLocalLDAP.BindDN, testConfigLocalLDAP.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) return @@ -262,13 +205,13 @@ func TestLocalLDAP_Authenticate(t *testing.T) { username := "professor" password := "professor" - filter := fmt.Sprintf("(uid=%s)", username) + filter := fmt.Sprintf("(%s=%s)", testConfigPublicLDAP.AttributeUserRDN, username) searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigLocalLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, filter, - []string{"mail"}, + userAttrs, nil, ) diff --git a/domain/auth/ldap/public_test.go b/domain/auth/ldap/public_test.go index 26f388f6..eb1eb6fe 100644 --- a/domain/auth/ldap/public_test.go +++ b/domain/auth/ldap/public_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 Documize Inc. . All rights reserved. +// Copyright 2016 Documize IntestConfigPublicLDAP. . All rights reserved. // // This software (Documize Community Edition) is licensed under // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html @@ -12,7 +12,6 @@ package ldap import ( - "crypto/tls" "fmt" "strings" "testing" @@ -23,61 +22,50 @@ import ( // Works against https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ +var testConfigPublicLDAP = lm.LDAPConfig{ + ServerType: lm.ServerTypeLDAP, + ServerHost: "ldap.forumsys.com", + ServerPort: 389, + EncryptionType: "none", + BaseDN: "dc=example,dc=com", + BindDN: "cn=read-only-admin,dc=example,dc=com", + BindPassword: "password", + UserFilter: "", + GroupFilter: "", + AttributeUserRDN: "uid", + AttributeUserFirstname: "givenName", + AttributeUserLastname: "sn", + AttributeUserEmail: "mail", + AttributeUserDisplayName: "", + AttributeUserGroupName: "", + AttributeGroupMember: "uniqueMember", +} + func TestPublicLDAPServer_UserList(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "ldap.forumsys.com" - c.ServerPort = 389 - c.EncryptionType = "none" - c.BaseDN = "dc=example,dc=com" - c.BindDN = "cn=read-only-admin,dc=example,dc=com" - c.BindPassword = "password" - c.UserFilter = "" - c.GroupFilter = "" + testConfigPublicLDAP.UserFilter = "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" + testConfigPublicLDAP.GroupFilter = "" + userAttrs := testConfigPublicLDAP.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - - t.Log("Connecting to LDAP server", address) - - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigPublicLDAP) if err != nil { t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with LDAP server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with LDAP server: ", err.Error()) - return - } - } - // Authenticate with LDAP server using admin credentials. t.Log("Binding LDAP admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigPublicLDAP.BindDN, testConfigPublicLDAP.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) return } - // Get users from LDAP server by using filter - filter := "" - attrs := []string{} - if len(c.GroupFilter) > 0 { - filter = fmt.Sprintf("(&(objectClass=group)(cn=%s))", c.GroupFilter) - attrs = []string{"cn"} - } else { - filter = "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } - searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - attrs, + testConfigPublicLDAP.UserFilter, + userAttrs, nil, ) @@ -94,75 +82,45 @@ func TestPublicLDAPServer_UserList(t *testing.T) { } for _, entry := range sr.Entries { - fmt.Printf("[%s] %s (%s %s) @ %s\n", - entry.GetAttributeValue("uid"), + t.Logf("[%s] %s (%s %s) @ %s\n", + entry.GetAttributeValue(testConfigPublicLDAP.AttributeUserRDN), entry.GetAttributeValue("cn"), - entry.GetAttributeValue("givenName"), - entry.GetAttributeValue("sn"), - entry.GetAttributeValue("mail")) + entry.GetAttributeValue(testConfigPublicLDAP.AttributeUserFirstname), + entry.GetAttributeValue(testConfigPublicLDAP.AttributeUserLastname), + entry.GetAttributeValue(testConfigPublicLDAP.AttributeUserEmail)) } } func TestPublicLDAPServer_Groups(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "ldap.forumsys.com" - c.ServerPort = 389 - c.EncryptionType = "none" - c.BaseDN = "dc=example,dc=com" - c.BindDN = "cn=read-only-admin,dc=example,dc=com" - c.BindPassword = "password" - c.UserFilter = "" - c.GroupFilter = "(|(ou=mathematicians)(ou=chemists))" + testConfigPublicLDAP.UserFilter = "" + testConfigPublicLDAP.GroupFilter = "(|(ou=mathematicians)(ou=chemists))" + groupAttrs := testConfigPublicLDAP.GetGroupFilterAttributes() + userAttrs := testConfigPublicLDAP.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - t.Log("Connecting to LDAP server", address) - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigPublicLDAP) if err != nil { t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with LDAP server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with LDAP server: ", err.Error()) - return - } - } - // Authenticate with LDAP server using admin credentials. t.Log("Binding LDAP admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigPublicLDAP.BindDN, testConfigPublicLDAP.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) return } - // Get users from LDAP server by using filter - filter := "" - attrs := []string{} - if len(c.GroupFilter) > 0 { - filter = c.GroupFilter - attrs = []string{"dn", "cn", "uniqueMember"} - } else if len(c.UserFilter) > 0 { - filter = c.UserFilter - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } else { - filter = "(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))" - attrs = []string{"dn", "cn", "givenName", "sn", "mail", "uid"} - } - searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - attrs, + testConfigPublicLDAP.GroupFilter, + groupAttrs, nil, ) - t.Log("LDAP search filter:", filter) + t.Log("LDAP search filter:", testConfigPublicLDAP.GroupFilter) sr, err := l.Search(searchRequest) if err != nil { t.Error("Error: unable to execute directory search: ", err.Error()) @@ -179,7 +137,7 @@ func TestPublicLDAPServer_Groups(t *testing.T) { for _, group := range sr.Entries { t.Log("Found group", group.DN) - rawMembers := group.GetAttributeValues("uniqueMember") + rawMembers := group.GetAttributeValues(testConfigPublicLDAP.AttributeGroupMember) if len(rawMembers) == 0 { t.Log("Error: group member attribute returned no users") continue @@ -196,10 +154,10 @@ func TestPublicLDAPServer_Groups(t *testing.T) { filter := fmt.Sprintf("(%s)", parts[0]) usr := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, filter, - []string{"dn", "cn", "givenName", "sn", "mail", "uid"}, + userAttrs, nil, ) ue, err := l.Search(usr) @@ -210,7 +168,12 @@ func TestPublicLDAPServer_Groups(t *testing.T) { if len(ue.Entries) > 0 { for _, ur := range ue.Entries { - t.Logf("%s", ur.GetAttributeValue("mail")) + t.Logf("[%s] %s (%s %s) @ %s\n", + ur.GetAttributeValue(testConfigPublicLDAP.AttributeUserRDN), + ur.GetAttributeValue("cn"), + ur.GetAttributeValue(testConfigPublicLDAP.AttributeUserFirstname), + ur.GetAttributeValue(testConfigPublicLDAP.AttributeUserLastname), + ur.GetAttributeValue(testConfigPublicLDAP.AttributeUserEmail)) } } else { t.Log("group member search failed:", filter) @@ -220,37 +183,20 @@ func TestPublicLDAPServer_Groups(t *testing.T) { } func TestPublicLDAP_Authenticate(t *testing.T) { - c := lm.LDAPConfig{} - c.ServerHost = "ldap.forumsys.com" - c.ServerPort = 389 - c.EncryptionType = "none" - c.BaseDN = "dc=example,dc=com" - c.BindDN = "cn=read-only-admin,dc=example,dc=com" - c.BindPassword = "password" - c.UserFilter = "" - c.GroupFilter = "" + testConfigPublicLDAP.UserFilter = "" + testConfigPublicLDAP.GroupFilter = "" + userAttrs := testConfigPublicLDAP.GetUserFilterAttributes() - address := fmt.Sprintf("%s:%d", c.ServerHost, c.ServerPort) - t.Log("Connecting to LDAP server", address) - l, err := ld.Dial("tcp", address) + l, err := Connect(testConfigPublicLDAP) if err != nil { - t.Error("Error: unable to dial AD server: ", err.Error()) + t.Error("Error: unable to dial LDAP server: ", err.Error()) return } defer l.Close() - if c.EncryptionType == "starttls" { - t.Log("Using StartTLS with LDAP server") - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - t.Error("Error: unable to startTLS with LDAP server: ", err.Error()) - return - } - } - // Authenticate with LDAP server using admin credentials. t.Log("Binding LDAP admin user") - err = l.Bind(c.BindDN, c.BindPassword) + err = l.Bind(testConfigPublicLDAP.BindDN, testConfigPublicLDAP.BindPassword) if err != nil { t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) return @@ -258,13 +204,13 @@ func TestPublicLDAP_Authenticate(t *testing.T) { username := "newton" password := "password" - filter := fmt.Sprintf("(uid=%s)", username) + filter := fmt.Sprintf("(%s=%s)", testConfigPublicLDAP.AttributeUserRDN, username) searchRequest := ld.NewSearchRequest( - c.BaseDN, + testConfigPublicLDAP.BaseDN, ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, filter, - []string{"mail"}, + userAttrs, nil, ) diff --git a/model/auth/ldap.go b/model/auth/ldap.go index f5970eef..b9fe5e60 100644 --- a/model/auth/ldap.go +++ b/model/auth/ldap.go @@ -11,6 +11,10 @@ package auth +import ( + "strings" +) + // Example for Active Directory -- filter users that belong to SomeGroupName: // (&(objectCategory=Person)(sAMAccountName=*)(memberOf=cn=SomeGroupName,ou=users,dc=example,dc=com)) // @@ -29,24 +33,116 @@ package auth // LDAPConfig that specifies LDAP server connection details and query filters. type LDAPConfig struct { - ServerHost string `json:"serverHost"` - ServerPort int `json:"serverPort"` - EncryptionType string `json:"encryptionType"` - BaseDN string `json:"baseDN"` - BindDN string `json:"bindDN"` - BindPassword string `json:"bindPassword"` - UserFilter string `json:"userFilter"` - GroupFilter string `json:"groupFilter"` - DisableLogout bool `json:"disableLogout"` - DefaultPermissionAddSpace bool `json:"defaultPermissionAddSpace"` - AttributeUserRDN string `json:"attributeUserRDN"` - AttributeUserID string `json:"attributeUserID"` // uid or sAMAccountName - AttributeUserFirstname string `json:"attributeUserFirstname"` - AttributeUserLastname string `json:"attributeUserLastname"` - AttributeUserEmail string `json:"attributeUserEmail"` - AttributeUserDisplayName string `json:"attributeUserDisplayName"` - AttributeUserGroupName string `json:"attributeUserGroupName"` - AttributeGroupMember string `json:"attributeGroupMember"` + ServerHost string `json:"serverHost"` + ServerPort int `json:"serverPort"` + ServerType ServerType `json:"serverType"` + EncryptionType string `json:"encryptionType"` + BaseDN string `json:"baseDN"` + BindDN string `json:"bindDN"` + BindPassword string `json:"bindPassword"` + UserFilter string `json:"userFilter"` + GroupFilter string `json:"groupFilter"` + DisableLogout bool `json:"disableLogout"` + DefaultPermissionAddSpace bool `json:"defaultPermissionAddSpace"` + AttributeUserRDN string `json:"attributeUserRDN"` // usually uid (LDAP) or sAMAccountName (AD) + AttributeUserFirstname string `json:"attributeUserFirstname"` // usually givenName + AttributeUserLastname string `json:"attributeUserLastname"` // usually sn + AttributeUserEmail string `json:"attributeUserEmail"` // usually mail + AttributeUserDisplayName string `json:"attributeUserDisplayName"` // usually displayName + AttributeUserGroupName string `json:"attributeUserGroupName"` // usually memberOf + AttributeGroupMember string `json:"attributeGroupMember"` // usually member +} + +// ServerType identifies the LDAP server type +type ServerType string + +const ( + // ServerTypeLDAP represents a generic LDAP server OpenLDAP. + ServerTypeLDAP = "ldap" + // ServerTypeAD represents Microsoft Active Directory server. + ServerTypeAD = "ad" +) + +// Clean ensures configuration data is formatted correctly. +func (c *LDAPConfig) Clean() { + c.BaseDN = strings.TrimSpace(c.BaseDN) + c.BindDN = strings.TrimSpace(c.BindDN) + c.BindPassword = strings.TrimSpace(c.BindPassword) + c.ServerHost = strings.TrimSpace(c.ServerHost) + c.EncryptionType = strings.TrimSpace(c.EncryptionType) + c.UserFilter = strings.TrimSpace(c.UserFilter) + c.GroupFilter = strings.TrimSpace(c.GroupFilter) + + if c.ServerPort == 0 { + c.ServerPort = 389 + } + if c.ServerType == "" { + c.ServerType = ServerTypeLDAP + } + if c.EncryptionType == "" { + c.EncryptionType = "none" + } + + c.AttributeUserRDN = strings.TrimSpace(c.AttributeUserRDN) + c.AttributeUserFirstname = strings.TrimSpace(c.AttributeUserFirstname) + c.AttributeUserLastname = strings.TrimSpace(c.AttributeUserLastname) + c.AttributeUserEmail = strings.TrimSpace(c.AttributeUserEmail) + c.AttributeUserDisplayName = strings.TrimSpace(c.AttributeUserDisplayName) + c.AttributeUserGroupName = strings.TrimSpace(c.AttributeUserGroupName) + + c.AttributeGroupMember = strings.TrimSpace(c.AttributeGroupMember) +} + +// GetUserFilterAttributes gathers the fields that can be requested +// when executing a user-based object filter. +func (c *LDAPConfig) GetUserFilterAttributes() []string { + a := []string{} + + // defaults + a = append(a, "dn") + a = append(a, "cn") + + if len(c.AttributeUserRDN) > 0 { + a = append(a, c.AttributeUserRDN) + } + + if len(c.AttributeUserFirstname) > 0 { + a = append(a, c.AttributeUserFirstname) + } + + if len(c.AttributeUserLastname) > 0 { + a = append(a, c.AttributeUserLastname) + } + + if len(c.AttributeUserEmail) > 0 { + a = append(a, c.AttributeUserEmail) + } + + if len(c.AttributeUserDisplayName) > 0 { + a = append(a, c.AttributeUserDisplayName) + } + + if len(c.AttributeUserGroupName) > 0 { + a = append(a, c.AttributeUserGroupName) + } + + return a +} + +// GetGroupFilterAttributes gathers the fields that can be requested +// when executing a group-based object filter. +func (c *LDAPConfig) GetGroupFilterAttributes() []string { + a := []string{} + + // defaults + a = append(a, "dn") + a = append(a, "cn") + + if len(c.AttributeGroupMember) > 0 { + a = append(a, c.AttributeGroupMember) + } + + return a } // LDAPUser details user record returned by LDAP