ldap-upstream/internal/ldap/bind_test.go
2026-11-01 18:29:16 -06:00

124 lines
3.9 KiB
Go

package ldap
import (
"context"
"errors"
"testing"
ldap "github.com/ps78674/ldapserver"
pb "github.com/netauth/protocol"
)
var _ naClient = (*fakeClient)(nil)
type fakeClient struct {
authErr error
authCalls []string
}
func (f *fakeClient) AuthEntity(ctx context.Context, id, password string) error {
f.authCalls = append(f.authCalls, id)
return f.authErr
}
func (f *fakeClient) EntitySearch(context.Context, string) ([]*pb.Entity, error) { return nil, nil }
func (f *fakeClient) EntityGroups(context.Context, string) ([]*pb.Group, error) { return nil, nil }
func (f *fakeClient) GroupSearch(context.Context, string) ([]*pb.Group, error) { return nil, nil }
func (f *fakeClient) GroupMembers(context.Context, string) ([]*pb.Entity, error) { return nil, nil }
func TestAnonBindSingleResponse(t *testing.T) {
c := &fakeClient{}
s := &server{l: testLogger(), c: c}
s.nc = []string{"dc=netauth", "dc=example", "dc=com"}
s.allowAnon = true
w := &recordingWriter{}
s.handleBind(w, mustDecode(t, berBindRequest("", "")))
if len(w.responses) != 1 {
t.Fatalf("got %d responses for an anonymous bind, want exactly 1", len(w.responses))
}
if code := resultCode(t, w.responses[0]); code != ldap.LDAPResultSuccess {
t.Errorf("result code is %d, want %d", code, ldap.LDAPResultSuccess)
}
if len(c.authCalls) != 0 {
t.Errorf("an anonymous bind reached NetAuth with %v", c.authCalls)
}
}
func TestAnonBindDisabled(t *testing.T) {
s := &server{l: testLogger(), c: &fakeClient{}}
s.nc = []string{"dc=netauth", "dc=example", "dc=com"}
w := &recordingWriter{}
s.handleBind(w, mustDecode(t, berBindRequest("", "")))
if len(w.responses) != 1 {
t.Fatalf("got %d responses, want exactly 1", len(w.responses))
}
if code := resultCode(t, w.responses[0]); code != ldap.LDAPResultInvalidDNSyntax {
t.Errorf("result code is %d, want %d", code, ldap.LDAPResultInvalidDNSyntax)
}
}
func TestBindValidCredentials(t *testing.T) {
c := &fakeClient{}
s := &server{l: testLogger(), c: c}
s.nc = []string{"dc=netauth", "dc=example", "dc=com"}
w := &recordingWriter{}
s.handleBind(w, mustDecode(t,
berBindRequest("uid=jsmith,ou=entities,dc=netauth,dc=example,dc=com", "hunter2")))
if len(w.responses) != 1 {
t.Fatalf("got %d responses, want exactly 1", len(w.responses))
}
if code := resultCode(t, w.responses[0]); code != ldap.LDAPResultSuccess {
t.Errorf("result code is %d, want %d", code, ldap.LDAPResultSuccess)
}
if len(c.authCalls) != 1 || c.authCalls[0] != "jsmith" {
t.Errorf("NetAuth authenticated %v, want [jsmith]", c.authCalls)
}
}
func TestBindInvalidCredentials(t *testing.T) {
c := &fakeClient{authErr: errors.New("invalid password")}
s := &server{l: testLogger(), c: c}
s.nc = []string{"dc=netauth", "dc=example", "dc=com"}
w := &recordingWriter{}
s.handleBind(w, mustDecode(t,
berBindRequest("uid=jsmith,ou=entities,dc=netauth,dc=example,dc=com", "wrong")))
if len(w.responses) != 1 {
t.Fatalf("got %d responses, want exactly 1", len(w.responses))
}
if code := resultCode(t, w.responses[0]); code != ldap.LDAPResultInvalidCredentials {
t.Errorf("result code is %d, want %d", code, ldap.LDAPResultInvalidCredentials)
}
}
func TestBindUnsupportedMechanism(t *testing.T) {
s := &server{l: testLogger(), c: &fakeClient{}}
s.nc = []string{"dc=netauth", "dc=example", "dc=com"}
// A bind request carrying SASL credentials rather than a simple
// password must be refused outright.
sasl := berElement(0x60,
berInteger(3),
berOctetString("uid=jsmith,ou=entities,dc=netauth,dc=example,dc=com"),
berElement(0xa3, berOctetString("GSSAPI")),
)
msg := berElement(0x30, berInteger(1), sasl)
w := &recordingWriter{}
s.handleBind(w, mustDecode(t, msg))
if len(w.responses) != 1 {
t.Fatalf("got %d responses, want exactly 1", len(w.responses))
}
if code := resultCode(t, w.responses[0]); code != ldap.LDAPResultUnwillingToPerform {
t.Errorf("result code is %d, want %d", code, ldap.LDAPResultUnwillingToPerform)
}
}