package ldap import ( "testing" "github.com/vjeantet/goldap/message" pb "github.com/netauth/protocol" ) func strptr(s string) *string { return &s } func intptr(i int32) *int32 { return &i } // messageSelection builds an AttributeSelection from plain strings. func messageSelection(vals []string) message.AttributeSelection { if vals == nil { return nil } sel := make(message.AttributeSelection, len(vals)) for i, v := range vals { sel[i] = message.LDAPString(v) } return sel } func testEntity(id string, number int32) *pb.Entity { return &pb.Entity{ID: strptr(id), Number: intptr(number)} } func testGroup(name string, number int32) *pb.Group { return &pb.Group{Name: strptr(name), Number: intptr(number)} } func TestAttributeRequested(t *testing.T) { cases := []struct { selection []string name string want bool }{ {nil, "memberOf", true}, {[]string{"*"}, "memberOf", true}, {[]string{"uid", "memberOf"}, "memberOf", true}, {[]string{"uid"}, "memberOf", false}, {[]string{"UID"}, "uid", true}, {[]string{"1.1"}, "memberOf", false}, {[]string{"member;binary"}, "member", true}, {[]string{"uid"}, "member", false}, } for _, tc := range cases { sel := messageSelection(tc.selection) if got := attributeRequested(sel, tc.name); got != tc.want { t.Errorf("selection %v for %q: got %t, want %t", tc.selection, tc.name, got, tc.want) } } } // bind marks the test connection as authenticated so a search is not // refused before it reaches the handler logic under test. func bind(t *testing.T, s *server) { t.Helper() s.handleBind(&recordingWriter{}, mustDecode(t, berBindRequest("uid=jsmith,ou=entities,dc=netauth,dc=example,dc=com", "hunter2"))) } // Membership costs one NetAuth round trip per entry, so it must only // happen when the client actually asked for the attribute. func TestEntitySearchSkipsUnrequestedGroups(t *testing.T) { c := &fakeClient{ entitySearchResults: []*pb.Entity{testEntity("jsmith", 1000)}, entityGroupsResults: map[string][]*pb.Group{"jsmith": {testGroup("admins", 10)}}, } s := boundTestServer(c) bind(t, s) w := &recordingWriter{} s.handleSearchEntities(w, mustDecode(t, berSearchRequestWithAttrs("ou=entities,dc=netauth,dc=example,dc=com", "uid"))) if len(w.responses) < 2 { t.Fatalf("the search did not return any entries: %d responses", len(w.responses)) } if len(c.entityGroupsCalls) != 0 { t.Errorf("EntityGroups was called %d times for a search that did not request memberOf", len(c.entityGroupsCalls)) } } func TestEntitySearchFetchesRequestedGroups(t *testing.T) { c := &fakeClient{ entitySearchResults: []*pb.Entity{testEntity("jsmith", 1000)}, entityGroupsResults: map[string][]*pb.Group{"jsmith": {testGroup("admins", 10)}}, } s := boundTestServer(c) bind(t, s) w := &recordingWriter{} s.handleSearchEntities(w, mustDecode(t, berSearchRequestWithAttrs("ou=entities,dc=netauth,dc=example,dc=com", "uid", "memberOf"))) if len(c.entityGroupsCalls) != 1 { t.Errorf("EntityGroups was called %d times, want 1", len(c.entityGroupsCalls)) } } func TestGroupSearchSkipsUnrequestedMembers(t *testing.T) { c := &fakeClient{ groupSearchResults: []*pb.Group{testGroup("admins", 10)}, groupMembersResults: map[string][]*pb.Entity{"admins": {testEntity("jsmith", 1000)}}, } s := boundTestServer(c) bind(t, s) w := &recordingWriter{} s.handleSearchGroups(w, mustDecode(t, berSearchRequestWithAttrs("ou=groups,dc=netauth,dc=example,dc=com", "cn"))) if len(w.responses) < 2 { t.Fatalf("the search did not return any entries: %d responses", len(w.responses)) } if len(c.groupMembersCalls) != 0 { t.Errorf("GroupMembers was called %d times for a search that did not request member", len(c.groupMembersCalls)) } } func TestGroupSearchFetchesRequestedMembers(t *testing.T) { c := &fakeClient{ groupSearchResults: []*pb.Group{testGroup("admins", 10)}, groupMembersResults: map[string][]*pb.Entity{"admins": {testEntity("jsmith", 1000)}}, } s := boundTestServer(c) bind(t, s) w := &recordingWriter{} s.handleSearchGroups(w, mustDecode(t, berSearchRequestWithAttrs("ou=groups,dc=netauth,dc=example,dc=com", "cn", "member"))) if len(c.groupMembersCalls) != 1 { t.Errorf("GroupMembers was called %d times, want 1", len(c.groupMembersCalls)) } }