Skip to content

Commit

Permalink
Merge pull request #77 from doncicuto/feat-54
Browse files Browse the repository at this point in the history
Feat 54
  • Loading branch information
doncicuto authored Oct 23, 2022
2 parents 2b51854 + 35b162f commit 0dc936b
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 9 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/dgraph-io/badger v1.6.2
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4
github.com/go-ldap/ldap v3.0.3+incompatible // indirect
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
Expand All @@ -36,6 +37,7 @@ require (
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/tools v0.1.11 // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gorm.io/driver/postgres v1.3.9 // indirect
gorm.io/driver/sqlite v1.3.4
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
Expand Down Expand Up @@ -1138,6 +1140,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
6 changes: 3 additions & 3 deletions server/api/handlers/group_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ func (h *Handler) AddMembers(g *models.Group, members []string) error {
for _, member := range members {
member = strings.TrimSpace(member)
// Find user
createdBy := new(models.User)
err = h.DB.Model(&models.User{}).Where("username = ?", member).Take(&createdBy).Error
user := new(models.User)
err = h.DB.Model(&models.User{}).Where("username = ?", member).Take(&user).Error
if err == nil {
// Append association
err = h.DB.Model(&g).Association("Members").Append(createdBy)
err = h.DB.Model(&g).Association("Members").Append(user)
if err != nil {
return err
}
Expand Down
90 changes: 90 additions & 0 deletions server/ldap/bind_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package ldap

import (
"net"
"testing"
"time"

ldapClient "github.com/go-ldap/ldap"
"github.com/google/uuid"
)

func TestBindOperation(t *testing.T) {
dbPath := uuid.New()
l, settings := testSetup(t, dbPath.String(), false, "127.0.0.1:60000")
defer testCleanUp(dbPath.String())

// Launch testing servers
go func() {
for {
// Accept new connections
c, err := l.Accept()
if err != nil {
return
}
// Handle our server connection
go handleConnection(c, settings)
}
}()

waitForTestServer(t, "127.0.0.1:60000")

// Create an Ldap connection
c, err := net.Dial("tcp", "127.0.0.1:60000")
if err != nil {
t.Fatalf("error connecting to localhost tcp: %v", err)
}
conn := ldapClient.NewConn(c, false)
conn.SetTimeout(3000 * time.Millisecond)
conn.Start()
defer conn.Close()

// Test cases
testCases := []BindTestCase{
{
name: "Bind successful",
username: "uid=saul,ou=Users,dc=example,dc=org",
password: "test",
conn: conn,
},
{
name: "Wrong password",
username: "uid=saul,ou=Users,dc=example,dc=org",
password: "test1",
conn: conn,
errorMessage: `LDAP Result Code 49 "Invalid Credentials": `,
},
{
name: "Wrong user",
username: "uid=test,ou=Users,dc=example,dc=org",
password: "test1",
conn: conn,
errorMessage: `LDAP Result Code 50 "Insufficient Access Rights": `,
},
{
name: "Empty password not allowed",
username: "uid=saul,ou=Users,dc=example,dc=org",
password: "",
conn: conn,
errorMessage: `LDAP Result Code 206 "Empty password not allowed by the client": ldap: empty password not allowed by the client`,
},
{
name: "Wrong domain",
username: "uid=saul,ou=Users,dc=example,dc=com",
password: "test1",
conn: conn,
errorMessage: `LDAP Result Code 49 "Invalid Credentials": `,
},
{
name: "Anonymous bind not allowed",
username: "",
password: "",
conn: conn,
errorMessage: `LDAP Result Code 206 "Empty password not allowed by the client": ldap: empty password not allowed by the client`,
},
}

for _, tc := range testCases {
runBindTests(t, tc)
}
}
172 changes: 172 additions & 0 deletions server/ldap/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package ldap

import (
"fmt"
"net"
"os"
"strings"
"testing"
"time"

"github.com/antelman107/net-wait-go/wait"
"github.com/doncicuto/glim/models"
"github.com/doncicuto/glim/server/db"
"github.com/doncicuto/glim/types"
ldapClient "github.com/go-ldap/ldap"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)

func addMembers(db *gorm.DB, group *models.Group, members string) error {
for _, member := range strings.Split(members, ",") {
user := new(models.User)
err := db.Model(&models.User{}).Where("username = ?", member).Take(&user).Error
if err == nil {
// Append association
err = db.Model(&group).Association("Members").Append(user)
if err != nil {
return err
}
}
}
return nil
}

func addGroup(db *gorm.DB, name string, description string, members string) error {
g := models.Group{}
g.Name = &name
g.Description = &description
err := db.Create(&g).Error
if err != nil {
return err
}
addMembers(db, &g, members)
return nil
}

func newTestDatabase(dbPath string) (*gorm.DB, error) {
var dbInit = types.DBInit{
AdminPasswd: "test",
SearchPasswd: "test",
Users: "saul,kim,mike",
DefaultPasswd: "test",
UseSqlite: true,
}
sqlLog := false
newDb, err := db.Initialize(fmt.Sprintf("/tmp/%s.db", dbPath), sqlLog, dbInit)
if err != nil {
return nil, err
}

// Create group test
err = addGroup(newDb, "test", "Test", "saul,kim")
if err != nil {
return nil, err
}

// Create group test2
err = addGroup(newDb, "test2", "Test2", "kim")
if err != nil {
return nil, err
}
return newDb, nil
}

func testSettings(db *gorm.DB, addr string) types.LDAPSettings {
return types.LDAPSettings{
DB: db,
TLSDisabled: true,
Address: addr,
Domain: "dc=example,dc=org",
}
}

func testSetup(t *testing.T, dbPath string, guacamole bool, addr string) (net.Listener, types.LDAPSettings) {
// New SQLite test database
db, err := newTestDatabase(dbPath)
if err != nil {
t.Fatalf("could not initialize db - %v", err)
}

settings := testSettings(db, addr)
settings.Guacamole = guacamole

var l net.Listener
l, err = net.Listen("tcp", addr)
if err != nil {
t.Fatalf("could not initialize socket - %v", err)
}
return l, settings
}

func testCleanUp(dbPath string) {
removeDatabase(dbPath)
}

func removeDatabase(dbPath string) {
os.Remove(fmt.Sprintf("/tmp/%s.db", dbPath))
}

type BindTestCase struct {
conn *ldapClient.Conn
name string
username string
password string
errorMessage string
}

type SearchTestCase struct {
conn *ldapClient.Conn
name string
baseDN string
scope int
sizeLimit int
timeLimit int
filter string
attributes []string
controls []ldapClient.Control
numEntries int
errorMessage string
}

func waitForTestServer(t *testing.T, address string) {
if !wait.New(
wait.WithProto("tcp"),
wait.WithWait(200*time.Millisecond),
wait.WithBreak(50*time.Millisecond),
wait.WithDeadline(5*time.Second),
wait.WithDebug(true),
).Do([]string{address}) {
t.Fatal("test server is not available")
return
}
}

func runBindTests(t *testing.T, tc BindTestCase) {
t.Run(tc.name, func(t *testing.T) {
err := tc.conn.Bind(tc.username, tc.password)
if err != nil {
assert.Equal(t, tc.errorMessage, fmt.Sprintf("%v", err.Error()))
} else {
if tc.errorMessage != "" {
t.Fatal(fmt.Errorf("error was expected"))
}
}
})
}

func runSearchTests(t *testing.T, tc SearchTestCase) {
t.Run(tc.name, func(t *testing.T) {
searchRequest := ldapClient.NewSearchRequest(tc.baseDN, tc.scope, ldapClient.DerefAlways, tc.sizeLimit, tc.timeLimit, false, tc.filter, tc.attributes, tc.controls)
sr, err := tc.conn.Search(searchRequest)
if err != nil {
assert.Equal(t, tc.errorMessage, fmt.Sprintf("%v", err.Error()))
} else {
if tc.errorMessage != "" {
t.Fatal(fmt.Errorf("error was expected"))
} else {
assert.Equal(t, tc.numEntries, len(sr.Entries))
}
}
})
}
86 changes: 86 additions & 0 deletions server/ldap/search_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ldap

import (
"net"
"testing"
"time"

ldapClient "github.com/go-ldap/ldap"
"github.com/google/uuid"
)

func TestSearchOperation(t *testing.T) {
dbPath := uuid.New()
l, settings := testSetup(t, dbPath.String(), false, "127.0.0.1:60001")
defer testCleanUp(dbPath.String())

// Launch testing servers
go func() {
for {
// Accept new connections
c, err := l.Accept()
if err != nil {
return
}
// Handle our server connection
go handleConnection(c, settings)
}
}()

waitForTestServer(t, "127.0.0.1:60001")

// Create an Ldap connection
c, err := net.Dial("tcp", "127.0.0.1:60001")
if err != nil {
t.Fatalf("error connecting to localhost tcp: %v", err)
}
conn := ldapClient.NewConn(c, false)
conn.SetTimeout(3000 * time.Millisecond)
conn.Start()
defer conn.Close()

// Bind
err = conn.Bind("cn=admin,dc=example,dc=org", "test")
if err != nil {
t.Fatalf("error in bind operation: %v", err)
}

// Test cases
testCases := []SearchTestCase{
{
name: "Search users successful",
conn: conn,
baseDN: "ou=Users,dc=example,dc=org",
scope: ldapClient.ScopeWholeSubtree,
filter: "(objectclass=*)",
attributes: []string{},
controls: nil,
numEntries: 4,
},
{
name: "Search groups successful",
conn: conn,
baseDN: "ou=Groups,dc=example,dc=org",
scope: ldapClient.ScopeWholeSubtree,
filter: "(objectclass=*)",
attributes: []string{},
controls: nil,
numEntries: 3,
},
{
name: "Wrong base",
conn: conn,
baseDN: "ou=Users,dc=example,dc=com",
scope: ldapClient.ScopeWholeSubtree,
filter: "(objectclass=*)",
attributes: []string{},
controls: nil,
numEntries: 0,
errorMessage: `LDAP Result Code 32 "No Such Object": `,
},
}

for _, tc := range testCases {
runSearchTests(t, tc)
}
}
Loading

0 comments on commit 0dc936b

Please sign in to comment.