diff --git a/entry_test.go b/entry_test.go index b951ace..ead72c6 100644 --- a/entry_test.go +++ b/entry_test.go @@ -76,7 +76,8 @@ func TestEntry_PrettyPrint(t *testing.T) { entry: &Entry{ DN: "uid=alice", Attributes: []*EntryAttribute{ - NewEntryAttribute("cn", []string{"alice"})}, + NewEntryAttribute("cn", []string{"alice"}), + }, }, writer: new(strings.Builder), want: " DN: uid=alice\n cn: [alice]\n", @@ -86,7 +87,8 @@ func TestEntry_PrettyPrint(t *testing.T) { entry: &Entry{ DN: "uid=alice", Attributes: []*EntryAttribute{ - NewEntryAttribute("cn", []string{"alice"})}, + NewEntryAttribute("cn", []string{"alice"}), + }, }, }, } diff --git a/packet.go b/packet.go index 04c88a2..110e1c3 100644 --- a/packet.go +++ b/packet.go @@ -461,7 +461,7 @@ func (p *packet) searchParmeters() (*searchParameters, error) { } searchFor.attributes = make([]string, 0, len(attributesPacket.Children)) for idx, attribute := range attributesPacket.Children { - if err := attributesPacket.assert(ber.ClassUniversal, ber.TypeConstructed, withTag(ber.TagOctetString), withAssertChild(idx)); err != nil { + if err := attributesPacket.assert(ber.ClassUniversal, ber.TypePrimitive, withTag(ber.TagOctetString), withAssertChild(idx)); err != nil { return nil, fmt.Errorf("%s: invalid attribute child packet: %w", op, err) } searchFor.attributes = append(searchFor.attributes, attribute.Data.String()) diff --git a/testdirectory/directory.go b/testdirectory/directory.go index 6d2fc64..4e68011 100644 --- a/testdirectory/directory.go +++ b/testdirectory/directory.go @@ -673,8 +673,8 @@ func (d *Directory) ClientKey() string { } // Controls returns all the current bind controls for the Directory -func (d *Directory) Controls() []*gldap.Entry { - return d.users +func (d *Directory) Controls() []gldap.Control { + return d.controls } // SetControls sets the bind controls. diff --git a/testdirectory/directory_test.go b/testdirectory/directory_test.go index bfd233b..219fc6c 100644 --- a/testdirectory/directory_test.go +++ b/testdirectory/directory_test.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "strings" "testing" "github.com/go-ldap/ldap/v3" @@ -76,6 +77,18 @@ func Test_Start(t *testing.T) { err := c.Bind(userDN, testPwd) require.NoError(err) + + clientCert, err := tls.X509KeyPair([]byte(td.ClientCert()), []byte(td.ClientKey())) + require.NoError(err) + certpool := x509.NewCertPool() + certpool.AppendCertsFromPEM([]byte(td.Cert())) + tlsConfig := &tls.Config{ + RootCAs: certpool, + Certificates: []tls.Certificate{clientCert}, + } + conn, err := ldap.DialURL(fmt.Sprintf("ldaps://localhost:%d", td.Port()), ldap.DialWithTLSConfig(tlsConfig)) + require.NoError(err) + conn.Close() }) t.Run("start-tls", func(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -104,6 +117,26 @@ func Test_Start(t *testing.T) { err = c.Bind(userDN, testPwd) require.NoError(err) }) + t.Run("start-with-TestingT", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + buf := &strings.Builder{} + bufLogger := hclog.New(&hclog.LoggerOptions{ + Name: "my-app", + Level: hclog.LevelFromString("DEBUG"), + Output: buf, + }) + l, err := testdirectory.NewLogger(bufLogger) + require.NoError(err) + assert.NotNil(l) + + td := testdirectory.Start(l, testdirectory.WithLogger(l, bufLogger)) + td.Stop() + assert.Contains(buf.String(), "stopped") + }) + t.Run("start-gldap.WithDisablePanicRecovery", func(t *testing.T) { + // not sure there's anything that's assertable here... + _ = testdirectory.Start(t, testdirectory.WithDisablePanicRecovery(t, true)) + }) } func TestDirectory_SimpleBindResponse(t *testing.T) { @@ -116,6 +149,11 @@ func TestDirectory_SimpleBindResponse(t *testing.T) { testdirectory.WithLogger(t, testLogger), testdirectory.WithDefaults(t, &testdirectory.Defaults{AllowAnonymousBind: true}), ) + + p, err := gldap.NewControlBeheraPasswordPolicy(gldap.WithGraceAuthNsRemaining(60)) + require.NoError(t, err) + td.SetControls(p) + users := testdirectory.NewUsers(t, []string{"alice", "bob"}) td.SetUsers(users...) @@ -164,10 +202,10 @@ func TestDirectory_SimpleBindResponse(t *testing.T) { } } -func TestDirectory_SearchUsersResponse(t *testing.T) { +func TestDirectory_SearchResponse(t *testing.T) { t.Parallel() testLogger := hclog.New(&hclog.LoggerOptions{ - Name: "TestDirectory_SearchUsersResponse-logger", + Name: "TestDirectory_SearchResponse-logger", Level: hclog.Error, }) @@ -204,7 +242,7 @@ func TestDirectory_SearchUsersResponse(t *testing.T) { name string filter string baseDN string - wantEntry *gldap.Entry + wantEntries []*gldap.Entry wantErr bool wantErrContains string }{ @@ -216,16 +254,22 @@ func TestDirectory_SearchUsersResponse(t *testing.T) { wantErrContains: `LDAP Result Code 32 "No Such Object"`, }, { - name: "alice-found", - filter: fmt.Sprintf("(%s=alice,%s)", testdirectory.DefaultUserAttr, testdirectory.DefaultUserDN), - baseDN: testdirectory.DefaultUserDN, - wantEntry: users[0], + name: "alice-found", + filter: fmt.Sprintf("(%s=alice,%s)", testdirectory.DefaultUserAttr, testdirectory.DefaultUserDN), + baseDN: testdirectory.DefaultUserDN, + wantEntries: []*gldap.Entry{users[0]}, }, { - name: "admin-group-found", - filter: fmt.Sprintf("(%s=admin,%s)", testdirectory.DefaultGroupAttr, testdirectory.DefaultGroupDN), - baseDN: testdirectory.DefaultGroupDN, - wantEntry: groups[0], + name: "admin-group-found", + filter: fmt.Sprintf("(%s=admin,%s)", testdirectory.DefaultGroupAttr, testdirectory.DefaultGroupDN), + baseDN: testdirectory.DefaultGroupDN, + wantEntries: []*gldap.Entry{groups[0]}, + }, + { + name: "token-group-found", + filter: fmt.Sprintf("(%s=admin,%s)", testdirectory.DefaultGroupAttr, testdirectory.DefaultGroupDN), + baseDN: "", + wantEntries: []*gldap.Entry{groups[0]}, }, { name: "group-not-found", @@ -235,10 +279,10 @@ func TestDirectory_SearchUsersResponse(t *testing.T) { wantErrContains: `LDAP Result Code 32 "No Such Object"`, }, { - name: "admin-member-found", - filter: fmt.Sprintf("(%s=alice,%s)", testdirectory.DefaultUserAttr, testdirectory.DefaultUserDN), - baseDN: testdirectory.DefaultGroupDN, - wantEntry: groups[0], + name: "admin-member-found", + filter: fmt.Sprintf("(%s=alice,%s)", testdirectory.DefaultUserAttr, testdirectory.DefaultUserDN), + baseDN: testdirectory.DefaultGroupDN, + wantEntries: []*gldap.Entry{groups[0]}, }, { name: "admin-member-not-found", @@ -248,10 +292,10 @@ func TestDirectory_SearchUsersResponse(t *testing.T) { wantErrContains: `LDAP Result Code 32 "No Such Object"`, }, { - name: "admin-member-found-upn", - filter: fmt.Sprintf("(userPrincipalName=eve@%s,%s)", "example.com", testdirectory.DefaultUserDN), - baseDN: testdirectory.DefaultGroupDN, - wantEntry: groups[1], + name: "admin-member-found-upn", + filter: fmt.Sprintf("(userPrincipalName=eve@%s,%s)", "example.com", testdirectory.DefaultUserDN), + baseDN: testdirectory.DefaultGroupDN, + wantEntries: []*gldap.Entry{groups[1]}, }, } @@ -260,9 +304,10 @@ func TestDirectory_SearchUsersResponse(t *testing.T) { assert, require := assert.New(t), require.New(t) client := td.Conn() defer func() { client.Close() }() - _, err := client.Search(&ldap.SearchRequest{ - BaseDN: tc.baseDN, - Filter: tc.filter, + results, err := client.Search(&ldap.SearchRequest{ + BaseDN: tc.baseDN, + Filter: tc.filter, + Attributes: []string{"name", "email", "password"}, }) if tc.wantErr { require.Error(err) @@ -272,6 +317,16 @@ func TestDirectory_SearchUsersResponse(t *testing.T) { return } assert.NoError(err) + found := []*gldap.Entry{} + for _, e := range results.Entries { + attrs := map[string][]string{} + for _, a := range e.Attributes { + attrs[a.Name] = a.Values + } + entry := gldap.NewEntry(e.DN, attrs) + found = append(found, entry) + } + assert.Equal(tc.wantEntries, found) }) } } @@ -503,8 +558,9 @@ func TestDirectory_AddResponse(t *testing.T) { } require.NoError(err) result, err := client.Search(&ldap.SearchRequest{ - BaseDN: tc.dn, - Filter: fmt.Sprintf("(%s)", tc.dn), + BaseDN: tc.dn, + Filter: fmt.Sprintf("(%s)", tc.dn), + Attributes: []string{"name", "email", "password"}, }) require.NoError(err) assert.Equal(len(tc.wantEntry.Attributes), len(result.Entries[0].Attributes)) @@ -515,3 +571,52 @@ func TestDirectory_AddResponse(t *testing.T) { }) } } + +func TestGetters(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + testLogger := hclog.New(&hclog.LoggerOptions{ + Name: "TestDirectory_AddResponse-logger", + Level: hclog.Error, + }) + td := testdirectory.Start(t, + testdirectory.WithLogger(t, testLogger), + ) + groups := []*gldap.Entry{ + testdirectory.NewGroup(t, "admin", []string{"alice"}), + testdirectory.NewGroup(t, "admin-upn", []string{"eve"}, testdirectory.WithDefaults(t, &testdirectory.Defaults{UPNDomain: "example.com"})), + } + + tokenGroups := map[string][]*gldap.Entry{ + "S-1-1": { + testdirectory.NewGroup(t, "admin", []string{"alice"}), + }, + } + sidBytes, err := gldap.SIDBytes(1, 1) + require.NoError(err) + + users := testdirectory.NewUsers(t, []string{"alice", "bob"}, testdirectory.WithMembersOf(t, "admin"), testdirectory.WithTokenGroups(t, sidBytes)) + users = append( + users, + testdirectory.NewUsers( + t, + []string{"eve"}, + testdirectory.WithDefaults(t, &testdirectory.Defaults{UPNDomain: "example.com"}), + testdirectory.WithMembersOf(t, "admin"))..., + ) + ctrl, err := gldap.NewControlBeheraPasswordPolicy(gldap.WithGraceAuthNsRemaining(60)) + require.NoError(err) + td.SetControls(ctrl) + + td.SetUsers(users...) + td.SetGroups(groups...) + td.SetTokenGroups(tokenGroups) + td.SetAllowAnonymousBind(true) + + assert.True(td.AllowAnonymousBind()) + assert.Equal(groups, td.Groups()) + assert.Equal(tokenGroups, td.TokenGroups()) + assert.Equal(users, td.Users()) + assert.Equal([]gldap.Control{ctrl}, td.Controls()) +} diff --git a/testdirectory/options_test.go b/testdirectory/options_test.go new file mode 100644 index 0000000..abdd2d4 --- /dev/null +++ b/testdirectory/options_test.go @@ -0,0 +1,78 @@ +package testdirectory + +import ( + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/jimlambrt/gldap" + "github.com/stretchr/testify/assert" +) + +func TestAllOptions(t *testing.T) { + testLogger := hclog.New(&hclog.LoggerOptions{ + Name: "my-app", + Level: hclog.LevelFromString("DEBUG"), + }) + t.Run("WithDefaults", func(t *testing.T) { + assert := assert.New(t) + opts := getOpts(t, + WithLogger(t, testLogger), + WithDefaults(t, &Defaults{ + UserAttr: "user-attr", + GroupAttr: "grp-attr", + Users: NewUsers(t, []string{"alice"}), + Groups: []*gldap.Entry{NewGroup(t, "admin", []string{"alice"})}, + UserDN: "user-dn", + GroupDN: "grp-dn", + TokenGroups: map[string][]*gldap.Entry{ + "S-1-1": { + NewGroup(t, "admin", []string{"alice"}), + }, + }, + AllowAnonymousBind: true, + UPNDomain: "domain", + })) + testOpts := defaults(t) + testOpts.withLogger = testLogger + testOpts.withDefaults.UserAttr = "user-attr" + testOpts.withDefaults.GroupAttr = "grp-attr" + testOpts.withDefaults.UserDN = "user-dn" + testOpts.withDefaults.GroupDN = "grp-dn" + testOpts.withDefaults.Users = NewUsers(t, []string{"alice"}) + testOpts.withDefaults.Groups = []*gldap.Entry{NewGroup(t, "admin", []string{"alice"})} + testOpts.withDefaults.TokenGroups = map[string][]*gldap.Entry{ + "S-1-1": { + NewGroup(t, "admin", []string{"alice"}), + }, + } + testOpts.withDefaults.AllowAnonymousBind = true + testOpts.withDefaults.UPNDomain = "domain" + assert.Equal(opts, testOpts) + }) + t.Run("withFirst", func(t *testing.T) { + assert := assert.New(t) + opts := getOpts(t, WithLogger(t, testLogger), withFirst(t)) + testOpts := defaults(t) + testOpts.withLogger = testLogger + testOpts.withFirst = true + assert.Equal(opts, testOpts) + }) + t.Run("WithDisablePanicRecovery", func(t *testing.T) { + assert := assert.New(t) + opts := getOpts(t, WithLogger(t, testLogger), WithDisablePanicRecovery(t, true)) + testOpts := defaults(t) + testOpts.withLogger = testLogger + testOpts.withDisablePanicRecovery = true + assert.Equal(opts, testOpts) + }) +} + +func Test_applyOpts(t *testing.T) { + assert := assert.New(t) + opts := options{} + applyOpts(&opts, nil) + assert.Equal(options{}, opts) + + applyOpts(&opts, withFirst(t)) + assert.Equal(options{withFirst: true}, opts) +} diff --git a/testdirectory/testing_test.go b/testdirectory/testing_test.go new file mode 100644 index 0000000..647cdcc --- /dev/null +++ b/testdirectory/testing_test.go @@ -0,0 +1,24 @@ +package testdirectory_test + +import ( + "testing" + + "github.com/jimlambrt/gldap/testdirectory" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewMemberOf(t *testing.T) { + t.Run("simple", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + DNs := testdirectory.NewMemberOf(t, + []string{"grp1", "grp2"}, + testdirectory.WithDefaults(t, &testdirectory.Defaults{ + GroupDN: "grp-dn", + GroupAttr: "grp-attr", + })) + require.Len(DNs, 2) + assert.Equal("grp-attr=grp1,grp-dn", DNs[0]) + assert.Equal("grp-attr=grp2,grp-dn", DNs[1]) + }) +} diff --git a/testdirectory/testingt_test.go b/testdirectory/testingt_test.go new file mode 100644 index 0000000..c73c7c6 --- /dev/null +++ b/testdirectory/testingt_test.go @@ -0,0 +1,76 @@ +package testdirectory_test + +import ( + "strings" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/jimlambrt/gldap/testdirectory" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Logger(t *testing.T) { + buf := &strings.Builder{} + bufLogger := hclog.New(&hclog.LoggerOptions{ + Name: "my-app", + Level: hclog.LevelFromString("DEBUG"), + Output: buf, + }) + t.Run("NewLogger", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + l, err := testdirectory.NewLogger(nil) + assert.Error(err) + assert.Nil(l) + + l, err = testdirectory.NewLogger(bufLogger) + require.NoError(err) + require.NotNil(l) + assert.Equal(bufLogger, l.Logger) + }) + t.Run("Errorf", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + l, err := testdirectory.NewLogger(bufLogger) + require.NoError(err) + require.NotNil(l) + buf.Reset() + + l.Errorf("test error") + assert.Contains(buf.String(), "[ERROR] my-app: test error") + buf.Reset() + }) + t.Run("Infof", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + l, err := testdirectory.NewLogger(bufLogger) + require.NoError(err) + require.NotNil(l) + buf.Reset() + + l.Infof("test info") + assert.Contains(buf.String(), "[INFO] my-app: test info") + buf.Reset() + }) + t.Run("Log", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + l, err := testdirectory.NewLogger(bufLogger) + require.NoError(err) + require.NotNil(l) + buf.Reset() + + l.Log("test log") + assert.Contains(buf.String(), "[INFO] my-app: test log") + buf.Reset() + }) + t.Run("FailNow", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + l, err := testdirectory.NewLogger(bufLogger) + require.NoError(err) + require.NotNil(l) + buf.Reset() + + assert.PanicsWithValue("testing.T failed, see logs for output (if any)", func() { + l.FailNow() + }) + }) +}