From fd167234aec41d6cc0f72f818b60640d1578b246 Mon Sep 17 00:00:00 2001 From: sauls8t Date: Sat, 1 Sep 2018 14:50:37 +0100 Subject: [PATCH] Discrete data loading functions --- domain/auth/ldap/ad_test.go | 95 ++++----------------------------- domain/auth/ldap/ldap.go | 71 +++++++++++++++++++++++- domain/auth/ldap/local_test.go | 93 ++++---------------------------- domain/auth/ldap/public_test.go | 93 ++++---------------------------- model/auth/ldap.go | 51 +++++++++++------- 5 files changed, 133 insertions(+), 270 deletions(-) diff --git a/domain/auth/ldap/ad_test.go b/domain/auth/ldap/ad_test.go index 0f9c7a0c..fbfdf4b2 100644 --- a/domain/auth/ldap/ad_test.go +++ b/domain/auth/ldap/ad_test.go @@ -12,12 +12,9 @@ package ldap import ( - "fmt" - "strings" "testing" lm "github.com/documize/community/model/auth" - ld "gopkg.in/ldap.v2" ) // Works against AD server in Azure configured using: @@ -30,7 +27,7 @@ var testConfigPublicAD = lm.LDAPConfig{ ServerType: lm.ServerTypeAD, ServerHost: "documize-ad.eastus.cloudapp.azure.com", ServerPort: 389, - EncryptionType: "none", + EncryptionType: lm.EncryptionTypeNone, BaseDN: "DC=mycompany,DC=local", BindDN: "CN=ad-admin,CN=Users,DC=mycompany,DC=local", BindPassword: "8B5tNRLvbk8K", @@ -54,7 +51,7 @@ func TestUserFilter_PublicAD(t *testing.T) { return } if len(e) == 0 { - t.Error("Received ZERO LDAP search entries") + t.Error("Received zero user LDAP search entries") return } @@ -66,94 +63,24 @@ func TestUserFilter_PublicAD(t *testing.T) { } } -func TestADServer_Groups(t *testing.T) { - testConfigPublicAD.UserFilter = "" +func TestGroupFilter_PublicAD(t *testing.T) { testConfigPublicAD.GroupFilter = "(|(cn=Accounting)(cn=IT))" - groupAttrs := testConfigPublicAD.GetGroupFilterAttributes() - userAttrs := testConfigPublicAD.GetUserFilterAttributes() - l, err := connect(testConfigPublicAD) + e, err := executeGroupFilter(testConfigPublicAD) if err != nil { - t.Error("Error: unable to dial AD server: ", err.Error()) + t.Error("unable to exeucte group filter", err.Error()) return } - defer l.Close() - - // 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()) + if len(e) == 0 { + t.Error("Received zero group LDAP search entries") return } - searchRequest := ld.NewSearchRequest( - testConfigPublicAD.BaseDN, - ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - testConfigPublicAD.GroupFilter, - groupAttrs, - nil, - ) + t.Logf("LDAP group search entries found: %d", len(e)) - 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()) - return - } - - t.Logf("AD search entries found: %d", len(sr.Entries)) - if len(sr.Entries) == 0 { - t.Error("Received ZERO AD search entries") - return - } - - // Get list of group members for each group found. - for _, group := range sr.Entries { - t.Log("Found group", group.DN) - - rawMembers := group.GetAttributeValues(testConfigPublicAD.AttributeGroupMember) - if len(rawMembers) == 0 { - t.Log("Error: group member attribute returned no users") - continue - } - - t.Logf("AD group contains %d members", len(rawMembers)) - - for _, entry := range rawMembers { - // get CN element from DN - parts := strings.Split(entry, ",") - if len(parts) == 0 { - continue - } - filter := fmt.Sprintf("(%s)", parts[0]) - - usr := ld.NewSearchRequest( - testConfigPublicAD.BaseDN, - ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - userAttrs, - nil, - ) - ue, err := l.Search(usr) - if err != nil { - t.Log("Error: unable to execute directory search for group member: ", err.Error()) - continue - } - - if len(ue.Entries) > 0 { - for _, ur := range ue.Entries { - t.Logf("[%s] %s (%s %s) @ %s\n", - ur.GetAttributeValue(testConfigPublicAD.AttributeUserRDN), - ur.GetAttributeValue("cn"), - ur.GetAttributeValue(testConfigPublicAD.AttributeUserFirstname), - ur.GetAttributeValue(testConfigPublicAD.AttributeUserLastname), - ur.GetAttributeValue(testConfigPublicAD.AttributeUserEmail)) - } - } else { - t.Log("group member search failed:", filter) - } - } + for _, u := range e { + t.Logf("[%s] %s (%s %s) @ %s\n", + u.RemoteID, u.CN, u.Firstname, u.Lastname, u.Email) } } diff --git a/domain/auth/ldap/ldap.go b/domain/auth/ldap/ldap.go index 879dae9c..b3019ce9 100644 --- a/domain/auth/ldap/ldap.go +++ b/domain/auth/ldap/ldap.go @@ -14,6 +14,7 @@ package ldap import ( "crypto/tls" "fmt" + "strings" lm "github.com/documize/community/model/auth" // "github.com/documize/community/model/user" @@ -33,7 +34,7 @@ func connect(c lm.LDAPConfig) (l *ld.Conn, err error) { return } - if c.EncryptionType == "starttls" { + if c.EncryptionType == lm.EncryptionTypeStartTLS { fmt.Println("Using StartTLS with LDAP server") err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { @@ -128,6 +129,74 @@ func executeUserFilter(c lm.LDAPConfig) (u []lm.LDAPUser, err error) { return } +// ExecuteGroupFilter returns all matching LDAP users that are paft of specified groups. +func executeGroupFilter(c lm.LDAPConfig) (u []lm.LDAPUser, err error) { + l, err := connect(c) + if err != nil { + err = errors.Wrap(err, "unable to dial LDAP server") + return + } + defer l.Close() + + // Authenticate with LDAP server using admin credentials. + err = l.Bind(c.BindDN, c.BindPassword) + if err != nil { + errors.Wrap(err, "unable to bind admin user") + return + } + + searchRequest := ld.NewSearchRequest( + c.BaseDN, + ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, + c.GroupFilter, + c.GetGroupFilterAttributes(), + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + errors.Wrap(err, "unable to execute directory search for user filter "+c.GroupFilter) + return + } + + for _, g := range sr.Entries { + rawMembers := g.GetAttributeValues(c.AttributeGroupMember) + if len(rawMembers) == 0 { + continue + } + + for _, entry := range rawMembers { + // get CN element from DN + parts := strings.Split(entry, ",") + if len(parts) == 0 { + continue + } + filter := fmt.Sprintf("(%s)", parts[0]) + + usr := ld.NewSearchRequest( + c.BaseDN, + ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, + filter, + c.GetUserFilterAttributes(), + nil, + ) + + ue, err := l.Search(usr) + if err != nil { + continue + } + + if len(ue.Entries) > 0 { + for _, ur := range ue.Entries { + u = append(u, extractUser(c, ur)) + } + } + } + } + + return +} + // extractUser build user record from LDAP result attributes. func extractUser(c lm.LDAPConfig, e *ld.Entry) (u lm.LDAPUser) { u.Firstname = e.GetAttributeValue(c.AttributeUserFirstname) diff --git a/domain/auth/ldap/local_test.go b/domain/auth/ldap/local_test.go index eb16f73f..6e762147 100644 --- a/domain/auth/ldap/local_test.go +++ b/domain/auth/ldap/local_test.go @@ -12,12 +12,9 @@ package ldap import ( - "fmt" - "strings" "testing" lm "github.com/documize/community/model/auth" - ld "gopkg.in/ldap.v2" ) // Works against https://github.com/rroemhild/docker-test-openldap @@ -27,7 +24,7 @@ var testConfigLocalLDAP = lm.LDAPConfig{ ServerType: lm.ServerTypeLDAP, ServerHost: "127.0.0.1", ServerPort: 389, - EncryptionType: "starttls", + EncryptionType: lm.EncryptionTypeStartTLS, BaseDN: "ou=people,dc=planetexpress,dc=com", BindDN: "cn=admin,dc=planetexpress,dc=com", BindPassword: "GoodNewsEveryone", @@ -63,94 +60,24 @@ func TestUserFilter_LocalLDAP(t *testing.T) { } } -func TestLocalLDAPServer_UsersInGroup(t *testing.T) { - testConfigLocalLDAP.UserFilter = "" +func TestGroupFilter_LocalLDAP(t *testing.T) { testConfigLocalLDAP.GroupFilter = "(&(objectClass=group)(|(cn=ship_crew)(cn=admin_staff)))" - groupAttrs := testConfigLocalLDAP.GetGroupFilterAttributes() - userAttrs := testConfigLocalLDAP.GetUserFilterAttributes() - l, err := connect(testConfigLocalLDAP) + e, err := executeGroupFilter(testConfigLocalLDAP) if err != nil { - t.Error("Error: unable to dial LDAP server: ", err.Error()) + t.Error("unable to exeucte group filter", err.Error()) return } - defer l.Close() - - // Authenticate with LDAP server using admin credentials. - t.Log("Binding LDAP admin user") - err = l.Bind(testConfigLocalLDAP.BindDN, testConfigLocalLDAP.BindPassword) - if err != nil { - t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) + if len(e) == 0 { + t.Error("Received zero group LDAP search entries") return } - searchRequest := ld.NewSearchRequest( - testConfigLocalLDAP.BaseDN, - ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - testConfigLocalLDAP.GroupFilter, - groupAttrs, - nil, - ) + t.Logf("LDAP group search entries found: %d", len(e)) - 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()) - return - } - - t.Logf("LDAP search entries found: %d", len(sr.Entries)) - if len(sr.Entries) == 0 { - t.Error("Received ZERO LDAP search entries") - return - } - - // Get list of group members per group found. - for _, group := range sr.Entries { - 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 - } - - t.Logf("LDAP group contains %d members", len(rawMembers)) - - for _, entry := range rawMembers { - // get CN element from DN - parts := strings.Split(entry, ",") - if len(parts) == 0 { - continue - } - filter := fmt.Sprintf("(%s)", parts[0]) - - usr := ld.NewSearchRequest( - testConfigLocalLDAP.BaseDN, - ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - userAttrs, - nil, - ) - ue, err := l.Search(usr) - if err != nil { - t.Log("Error: unable to execute directory search for group member: ", err.Error()) - continue - } - - if len(ue.Entries) > 0 { - for _, ur := range ue.Entries { - 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) - } - } + for _, u := range e { + t.Logf("[%s] %s (%s %s) @ %s\n", + u.RemoteID, u.CN, u.Firstname, u.Lastname, u.Email) } } diff --git a/domain/auth/ldap/public_test.go b/domain/auth/ldap/public_test.go index c48d09be..2d36c307 100644 --- a/domain/auth/ldap/public_test.go +++ b/domain/auth/ldap/public_test.go @@ -12,12 +12,9 @@ package ldap import ( - "fmt" - "strings" "testing" lm "github.com/documize/community/model/auth" - ld "gopkg.in/ldap.v2" ) // Works against https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ @@ -26,7 +23,7 @@ var testConfigPublicLDAP = lm.LDAPConfig{ ServerType: lm.ServerTypeLDAP, ServerHost: "ldap.forumsys.com", ServerPort: 389, - EncryptionType: "none", + EncryptionType: lm.EncryptionTypeNone, BaseDN: "dc=example,dc=com", BindDN: "cn=read-only-admin,dc=example,dc=com", BindPassword: "password", @@ -62,94 +59,24 @@ func TestUserFilter_PublicLDAP(t *testing.T) { } } -func TestPublicLDAPServer_Groups(t *testing.T) { - testConfigPublicLDAP.UserFilter = "" +func TestGroupFilter_PublicLDAP(t *testing.T) { testConfigPublicLDAP.GroupFilter = "(|(ou=mathematicians)(ou=chemists))" - groupAttrs := testConfigPublicLDAP.GetGroupFilterAttributes() - userAttrs := testConfigPublicLDAP.GetUserFilterAttributes() - l, err := connect(testConfigPublicLDAP) + e, err := executeGroupFilter(testConfigPublicLDAP) if err != nil { - t.Error("Error: unable to dial LDAP server: ", err.Error()) + t.Error("unable to exeucte group filter", err.Error()) return } - defer l.Close() - - // Authenticate with LDAP server using admin credentials. - t.Log("Binding LDAP admin user") - err = l.Bind(testConfigPublicLDAP.BindDN, testConfigPublicLDAP.BindPassword) - if err != nil { - t.Error("Error: unable to bind specified admin user to LDAP: ", err.Error()) + if len(e) == 0 { + t.Error("Received zero group LDAP search entries") return } - searchRequest := ld.NewSearchRequest( - testConfigPublicLDAP.BaseDN, - ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - testConfigPublicLDAP.GroupFilter, - groupAttrs, - nil, - ) + t.Logf("LDAP group search entries found: %d", len(e)) - 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()) - return - } - - t.Logf("LDAP search entries found: %d", len(sr.Entries)) - if len(sr.Entries) == 0 { - t.Error("Received ZERO LDAP search entries") - return - } - - // Get list of group members per group found. - for _, group := range sr.Entries { - t.Log("Found group", group.DN) - - rawMembers := group.GetAttributeValues(testConfigPublicLDAP.AttributeGroupMember) - if len(rawMembers) == 0 { - t.Log("Error: group member attribute returned no users") - continue - } - - t.Logf("LDAP group contains %d members", len(rawMembers)) - - for _, entry := range rawMembers { - // get CN element from DN - parts := strings.Split(entry, ",") - if len(parts) == 0 { - continue - } - filter := fmt.Sprintf("(%s)", parts[0]) - - usr := ld.NewSearchRequest( - testConfigPublicLDAP.BaseDN, - ld.ScopeWholeSubtree, ld.NeverDerefAliases, 0, 0, false, - filter, - userAttrs, - nil, - ) - ue, err := l.Search(usr) - if err != nil { - t.Log("Error: unable to execute directory search for group member: ", err.Error()) - continue - } - - if len(ue.Entries) > 0 { - for _, ur := range ue.Entries { - 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) - } - } + for _, u := range e { + t.Logf("[%s] %s (%s %s) @ %s\n", + u.RemoteID, u.CN, u.Firstname, u.Lastname, u.Email) } } diff --git a/model/auth/ldap.go b/model/auth/ldap.go index 077996b0..3932612c 100644 --- a/model/auth/ldap.go +++ b/model/auth/ldap.go @@ -33,24 +33,24 @@ import ( // LDAPConfig that specifies LDAP server connection details and query filters. type LDAPConfig struct { - 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 + ServerHost string `json:"serverHost"` + ServerPort int `json:"serverPort"` + ServerType ServerType `json:"serverType"` + EncryptionType EncryptionType `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 @@ -63,13 +63,23 @@ const ( ServerTypeAD = "ad" ) +// EncryptionType determines encryption method for LDAP connection.EncryptionType +type EncryptionType string + +const ( + // EncryptionTypeNone is none. + EncryptionTypeNone = "none" + + // EncryptionTypeStartTLS is using start TLS. + EncryptionTypeStartTLS = "starttls" +) + // 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) @@ -82,6 +92,9 @@ func (c *LDAPConfig) Clean() { if c.EncryptionType == "" { c.EncryptionType = "none" } + if c.EncryptionType != EncryptionTypeNone || c.EncryptionType != EncryptionTypeStartTLS { + c.EncryptionType = EncryptionTypeNone + } c.AttributeUserRDN = strings.TrimSpace(c.AttributeUserRDN) c.AttributeUserFirstname = strings.TrimSpace(c.AttributeUserFirstname)