Files
ldaps/server_modify_test.go
2026-08-25 14:48:54 -07:00

241 lines
7.3 KiB
Go

package ldaps
import (
"context"
"log"
"net"
"os/exec"
"strings"
"testing"
"github.com/go-ldap/ldap/v3"
)
func TestAdd(t *testing.T) {
s := NewServer()
s.BindFunc("", modifyTestHandler{})
s.AddFunc("", modifyTestHandler{})
addr, err := ListenAndServe(t, s)
if err != nil {
t.Errorf("Failed to listen")
return
}
t.Cleanup(s.Close)
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "ldapadd", "-v", "-H", "ldap://"+addr.String(), "-x", "-f", "tests/add.ldif")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("ldapadd failed: error(%v): %s", err, out)
}
if !strings.Contains(string(out), "modify complete") {
t.Errorf("ldapadd failed: %s", out)
}
}
func TestAddFail(t *testing.T) {
previousOutput := log.Writer()
log.SetOutput(t.Output())
t.Cleanup(func() { log.SetOutput(previousOutput) })
s := NewServer()
s.BindFunc("", modifyTestHandler{})
s.AddFunc("", modifyTestHandler{})
addr, err := ListenAndServe(t, s)
if err != nil {
t.Errorf("Failed to listen")
return
}
t.Cleanup(s.Close)
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "ldapadd", "-v", "-H", "ldap://"+addr.String(), "-x", "-f", "tests/add2.ldif")
out, err := cmd.CombinedOutput()
if err == nil {
t.Errorf("ldapadd succeed. It shouldn't have: %s", out)
}
if !strings.Contains(string(out), "ldap_add: Insufficient access") {
t.Errorf("ldapadd should have failed: %s", out)
}
if strings.Contains(string(out), "modify complete") {
t.Errorf("ldapadd should have failed: %s", out)
}
}
func TestDelete(t *testing.T) {
s := NewServer()
s.BindFunc("", modifyTestHandler{})
s.DeleteFunc("", modifyTestHandler{})
addr, err := ListenAndServe(t, s)
if err != nil {
t.Errorf("Failed to listen")
return
}
t.Cleanup(s.Close)
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "ldapdelete", "-v", "-H", "ldap://"+addr.String(), "-x", "cn=Delete Me,dc=example,dc=com")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("ldapdelete failed: error(%v): %s", err, out)
}
if cmd.ProcessState.ExitCode() != 0 {
t.Errorf("ldapdelete failed: %s", out)
}
}
func TestDeleteFail(t *testing.T) {
previousOutput := log.Writer()
log.SetOutput(t.Output())
t.Cleanup(func() { log.SetOutput(previousOutput) })
s := NewServer()
s.BindFunc("", modifyTestHandler{})
s.DeleteFunc("", modifyTestHandler{})
addr, err := ListenAndServe(t, s)
if err != nil {
t.Errorf("Failed to listen")
return
}
t.Cleanup(s.Close)
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "ldapdelete", "-v", "-H", "ldap://"+addr.String(), "-x", "cn=Bob,dc=example,dc=com")
out, err := cmd.CombinedOutput()
if err == nil {
t.Errorf("ldapdelete succeed. It shouldn't have: %s", out)
}
if strings.Contains(string(out), "Success") || !strings.Contains(string(out), "ldap_delete: Insufficient access") {
t.Errorf("ldapdelete should have failed: %s", out)
}
}
func TestModify(t *testing.T) {
s := NewServer()
s.BindFunc("", modifyTestHandler{})
s.ModifyFunc("", modifyTestHandler{})
addr, err := ListenAndServe(t, s)
if err != nil {
t.Errorf("Failed to listen")
return
}
t.Cleanup(s.Close)
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "ldapmodify", "-v", "-H", "ldap://"+addr.String(), "-x", "-f", "tests/modify.ldif")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("ldapmodify failed: error(%v): %s", err, out) // TODO:
}
if !strings.Contains(string(out), "modify complete") {
t.Errorf("ldapmodify failed: %s", out)
return
}
}
func TestModifyFail(t *testing.T) {
previousOutput := log.Writer()
log.SetOutput(t.Output())
t.Cleanup(func() { log.SetOutput(previousOutput) })
s := NewServer()
s.BindFunc("", modifyTestHandler{})
s.ModifyFunc("", modifyTestHandler{})
addr, err := ListenAndServe(t, s)
if err != nil {
t.Errorf("Failed to listen")
return
}
t.Cleanup(s.Close)
ctx, cancel := context.WithTimeout(t.Context(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "ldapmodify", "-v", "-H", "ldap://"+addr.String(), "-x", "-f", "tests/modify2.ldif")
out, err := cmd.CombinedOutput()
if err == nil {
t.Errorf("ldapmodify succeed. It shouldn't have: %s", out)
}
if !strings.Contains(string(out), "ldap_modify: Insufficient access") || strings.Contains(string(out), "modify complete") {
t.Errorf("ldapmodify should have failed: %s", out)
return
}
}
type modifyTestHandler struct{}
func (h modifyTestHandler) Bind(ctx context.Context, bindDN, bindSimplePw string, conn net.Conn) (*ldap.SimpleBindResult, error) {
if bindDN == "" && bindSimplePw == "" {
return nil, nil
}
return nil, ldap.NewError(ldap.LDAPResultInvalidCredentials, ErrEmpty)
}
func (h modifyTestHandler) Add(ctx context.Context, boundDN string, req ldap.AddRequest, conn net.Conn) error {
// only succeed on expected contents of add.ldif:
if len(req.Attributes) == 5 && req.DN == "cn=Barbara Jensen,dc=example,dc=com" && req.Attributes[2].Type == "sn" && len(req.Attributes[2].Vals) == 1 && req.Attributes[2].Vals[0] == "Jensen" {
return nil
}
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, ErrEmpty)
}
func (h modifyTestHandler) Delete(ctx context.Context, boundDN, deleteDN string, conn net.Conn) error {
// only succeed on expected deleteDN
if deleteDN == "cn=Delete Me,dc=example,dc=com" {
return nil
}
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, ErrEmpty)
}
func extractChanges(req ldap.ModifyRequest) (deleteAttributes []ldap.Change, replaceAttributes []ldap.Change, addAttributes []ldap.Change, incrementAttributes []ldap.Change, otherAttributes []ldap.Change) {
for _, change := range req.Changes {
switch change.Operation {
case ldap.AddAttribute:
addAttributes = append(addAttributes, change)
case ldap.ReplaceAttribute:
replaceAttributes = append(replaceAttributes, change)
case ldap.DeleteAttribute:
deleteAttributes = append(deleteAttributes, change)
case ldap.IncrementAttribute:
incrementAttributes = append(incrementAttributes, change)
default:
otherAttributes = append(otherAttributes, change)
}
}
return addAttributes, deleteAttributes, replaceAttributes, incrementAttributes, otherAttributes
}
func (h modifyTestHandler) Modify(ctx context.Context, boundDN string, req ldap.ModifyRequest, conn net.Conn) (*ldap.ModifyResult, error) {
// only succeed on expected contents of modify.ldif:
addAttributes, deleteAttributes, replaceAttributes, incrementAttributes, otherAttributes := extractChanges(req)
if req.DN == "cn=testy,dc=example,dc=com" &&
len(incrementAttributes) == 0 &&
len(otherAttributes) == 0 &&
len(addAttributes) == 1 &&
len(deleteAttributes) == 3 &&
len(replaceAttributes) == 2 &&
deleteAttributes[2].Modification.Type == "details" &&
len(deleteAttributes[2].Modification.Vals) == 0 {
return &ldap.ModifyResult{}, nil
}
return &ldap.ModifyResult{}, ldap.NewError(ldap.LDAPResultInsufficientAccessRights, ErrEmpty)
}
func (h modifyTestHandler) ModifyDN(ctx context.Context, boundDN string, req ldap.ModifyDNRequest, conn net.Conn) error {
// TODO: Implement a test for this
return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, ErrEmpty)
}