Skip to content

Commit

Permalink
Merge pull request #9 from jimlambrt/jimlambrt-another-refactor-with-…
Browse files Browse the repository at this point in the history
…tests

refactor/tests: another round of improving a few bits and adding tests
  • Loading branch information
jimlambrt authored Jan 26, 2022
2 parents 7ceda4a + e636276 commit ad93cf2
Show file tree
Hide file tree
Showing 15 changed files with 409 additions and 32 deletions.
62 changes: 55 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Example:
package main

import (
"context"
"log"
"os"
"os/signal"
Expand All @@ -46,17 +47,18 @@ func main() {
log.Fatalf("unable to create router: %s", err.Error())
}
r.Bind(bindHandler)
r.Search(searchHandler)
s.Router(r)

go s.Run(":10389") // listen on port 10389

// stop server gracefully when ctrl-c, sigint or sigterm occurs
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
close(ch)

s.Stop()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
select {
case <-ctx.Done():
log.Printf("\nstopping directory")
s.Stop()
}
}

func bindHandler(w *gldap.ResponseWriter, r *gldap.Request) {
Expand All @@ -78,6 +80,51 @@ func bindHandler(w *gldap.ResponseWriter, r *gldap.Request) {
log.Println("bind success")
return
}

func searchHandler(w *gldap.ResponseWriter, r *gldap.Request) {
resp := r.NewSearchDoneResponse()
defer func() {
w.Write(resp)
}()
m, err := r.GetSearchMessage()
if err != nil {
log.Printf("not a search message: %s", err)
return
}
log.Printf("search base dn: %s", m.BaseDN)
log.Printf("search scope: %d", m.Scope)
log.Printf("search filter: %s", m.Filter)

if strings.Contains(m.Filter, "uid=alice") || m.BaseDN == "uid=alice,ou=people,cn=example,dc=org" {
entry := r.NewSearchResponseEntry(
"uid=alice,ou=people,cn=example,dc=org",
gldap.WithAttributes(map[string][]string{
"objectclass": {"top", "person", "organizationalPerson", "inetOrgPerson"},
"uid": {"alice"},
"cn": {"alice eve smith"},
"givenname": {"alice"},
"sn": {"smith"},
"ou": {"people"},
"description": {"friend of Rivest, Shamir and Adleman"},
"password": {"{SSHA}U3waGJVC7MgXYc0YQe7xv7sSePuTP8zN"},
}),
)
entry.AddAttribute("email", []string{"[email protected]"})
w.Write(entry)
resp.SetResultCode(gldap.ResultSuccess)
}
if m.BaseDN == "ou=people,cn=example,dc=org" {
entry := r.NewSearchResponseEntry(
"ou=people,cn=example,dc=org",
gldap.WithAttributes(map[string][]string{
"objectclass": {"organizationalUnit"},
"ou": {"people"},
}),
)
w.Write(entry)
resp.SetResultCode(gldap.ResultSuccess)
}
return
}
```
<hr>
Expand Down Expand Up @@ -113,3 +160,4 @@ service much easier.
`testdirectory` is also a great working example of how you can use `gldap` to build a custom
ldap server to meet your specific needs.
8 changes: 4 additions & 4 deletions codes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package gldap

// Result Codes
// ldap result codes
const (
ResultSuccess = 0
ResultOperationsError = 1
Expand Down Expand Up @@ -77,7 +77,7 @@ const (
ResultSyncRefreshRequired = 4096
)

// ResultCodeMap contains string descriptions for error codes
// ResultCodeMap contains string descriptions for ldap result codes
var ResultCodeMap = map[uint16]string{
ResultSuccess: "Success",
ResultOperationsError: "Operations Error",
Expand Down Expand Up @@ -154,7 +154,7 @@ var ResultCodeMap = map[uint16]string{
ResultAuthorizationDenied: "Authorization Denied",
}

// Application Codes
// ldap application codes
const (
ApplicationBindRequest = 0
ApplicationBindResponse = 1
Expand All @@ -178,7 +178,7 @@ const (
ApplicationExtendedResponse = 24
)

// ApplicationCodeMap contains human readable descriptions of Application Codes
// ApplicationCodeMap contains human readable descriptions of ldap application codes
var ApplicationCodeMap = map[uint8]string{
ApplicationBindRequest: "Bind Request",
ApplicationBindResponse: "Bind Response",
Expand Down
21 changes: 21 additions & 0 deletions control.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gldap

import ber "github.com/go-asn1-ber/asn1-ber"

// Control defines a common interface for all ldap controls
type Control interface {
// GetControlType returns the OID
GetControlType() string
// Encode returns the ber packet representation
Encode() *ber.Packet
// String returns a human-readable description
String() string
}

func encodeControls(controls []Control) *ber.Packet {
packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
for _, control := range controls {
packet.AppendChild(control.Encode())
}
return packet
}
1 change: 0 additions & 1 deletion examples/simple-bind/.gitignore

This file was deleted.

1 change: 1 addition & 0 deletions examples/simple/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
simple
34 changes: 32 additions & 2 deletions examples/simple-bind/main.go → examples/simple/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"os"
"os/signal"
"strings"

"github.com/hashicorp/go-hclog"
"github.com/jimlambrt/gldap"
Expand All @@ -29,7 +30,7 @@ func main() {
log.Fatalf("unable to create router: %s", err.Error())
}
r.Bind(bindHandler)
r.Search(searchHandler)
r.Search(searchHandler, gldap.WithLabel("All Searches"))
s.Router(r)
go s.Run(":10389") // listen on port 10389

Expand Down Expand Up @@ -64,7 +65,7 @@ func bindHandler(w *gldap.ResponseWriter, r *gldap.Request) {
}

func searchHandler(w *gldap.ResponseWriter, r *gldap.Request) {
resp := r.NewSearchDoneResponse()
resp := r.NewSearchDoneResponse(gldap.WithResponseCode(gldap.ResultNoSuchObject))
defer func() {
w.Write(resp)
}()
Expand All @@ -77,5 +78,34 @@ func searchHandler(w *gldap.ResponseWriter, r *gldap.Request) {
log.Printf("search scope: %d", m.Scope)
log.Printf("search filter: %s", m.Filter)

if strings.Contains(m.Filter, "uid=alice") || m.BaseDN == "uid=alice,ou=people,cn=example,dc=org" {
entry := r.NewSearchResponseEntry(
"uid=alice,ou=people,cn=example,dc=org",
gldap.WithAttributes(map[string][]string{
"objectclass": {"top", "person", "organizationalPerson", "inetOrgPerson"},
"uid": {"alice"},
"cn": {"alice eve smith"},
"givenname": {"alice"},
"sn": {"smith"},
"ou": {"people"},
"description": {"friend of Rivest, Shamir and Adleman"},
"password": {"{SSHA}U3waGJVC7MgXYc0YQe7xv7sSePuTP8zN"},
}),
)
entry.AddAttribute("email", []string{"[email protected]"})
w.Write(entry)
resp.SetResultCode(gldap.ResultSuccess)
}
if m.BaseDN == "ou=people,cn=example,dc=org" {
entry := r.NewSearchResponseEntry(
"ou=people,cn=example,dc=org",
gldap.WithAttributes(map[string][]string{
"objectclass": {"organizationalUnit"},
"ou": {"people"},
}),
)
w.Write(entry)
resp.SetResultCode(gldap.ResultSuccess)
}
return
}
9 changes: 5 additions & 4 deletions message.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gldap

import "fmt"
import (
"fmt"
)

// Scope represents the scope of a search (see: https://ldap.com/the-ldap-search-operation/)
type Scope int64
Expand Down Expand Up @@ -43,6 +45,7 @@ const (

// Message defines a common interface for all messages
type Message interface {
// GetID returns the message ID
GetID() int64
}

Expand All @@ -57,7 +60,7 @@ func (m baseMessage) GetID() int64 { return m.id }
// SearchMessage is a search request message
type SearchMessage struct {
baseMessage
// BaseObject for the request
// BaseDN for the request
BaseDN string
// Scope of the request
Scope Scope
Expand All @@ -77,8 +80,6 @@ type SearchMessage struct {
Controls []Control
}

type Control string

// SimpleBindMesssage is a simple bind request message
type SimpleBindMessage struct {
baseMessage
Expand Down
4 changes: 3 additions & 1 deletion mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (m *Mux) Bind(bindFn HandlerFunc, opt ...Option) error {
}

// Search will register a handler for search requests.
// Options supported: WithLabel, WithBaseDN, WithScope
func (m *Mux) Search(searchFn HandlerFunc, opt ...Option) error {
const op = "gldap.(Mux).Search"
if searchFn == nil {
Expand All @@ -68,6 +69,7 @@ func (m *Mux) Search(searchFn HandlerFunc, opt ...Option) error {
}

// ExtendedOperation will register a handler for extended operation requests.
// Options supported: WithLabel
func (m *Mux) ExtendedOperation(operationFn HandlerFunc, exName ExtendedOperationName, opt ...Option) error {
const op = "gldap.(Mux).Search"
if operationFn == nil {
Expand Down Expand Up @@ -101,7 +103,7 @@ func (m *Mux) DefaultRoute(noRouteFN HandlerFunc, opt ...Option) error {
}
m.mu.Lock()
defer m.mu.Unlock()
m.routes = append(m.routes, r)
m.defaultRoute = r
return nil
}

Expand Down
96 changes: 96 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package gldap

import (
"bufio"
"bytes"
"fmt"
"strings"
"sync"
"testing"
"time"

"github.com/go-ldap/ldap/v3"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMux_serve(t *testing.T) {
t.Run("no-matching-handler", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
var buf bytes.Buffer
testLogger := hclog.New(&hclog.LoggerOptions{
Name: "TestServer_Run-logger",
Level: hclog.Debug,
Output: &buf,
})
s, err := NewServer(WithLogger(testLogger))
require.NoError(err)
port := freePort(t)
go func() {
err = s.Run(fmt.Sprintf(":%d", port))
assert.NoError(err)
}()
defer s.Stop()
time.Sleep(1 * time.Millisecond)
client, err := ldap.DialURL(fmt.Sprintf("ldap://localhost:%d", port))
require.NoError(err)
defer client.Close()
err = client.UnauthenticatedBind("alice")
require.Error(err)
assert.Contains(strings.ToLower(err.Error()), "no matching handler found")
})
t.Run("default-route", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
var buf bytes.Buffer
testLogger := hclog.New(&hclog.LoggerOptions{
Name: "TestServer_Run-logger",
Level: hclog.Debug,
Output: &buf,
})
s, err := NewServer(WithLogger(testLogger))
require.NoError(err)

mux, err := NewMux()
require.NoError(err)
mux.DefaultRoute(func(w *ResponseWriter, req *Request) {
resp := req.NewResponse(WithResponseCode(ResultUnwillingToPerform), WithDiagnosticMessage("default handler"))
_ = w.Write(resp)
})
s.Router(mux)

port := freePort(t)
go func() {
err = s.Run(fmt.Sprintf(":%d", port))
assert.NoError(err)
}()
defer s.Stop()
time.Sleep(1 * time.Millisecond)
client, err := ldap.DialURL(fmt.Sprintf("ldap://localhost:%d", port))
require.NoError(err)
defer client.Close()
err = client.UnauthenticatedBind("alice")
require.Error(err)
assert.Contains(strings.ToLower(err.Error()), "default handler")
})
t.Run("bad-parameters", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
m := &Mux{}
assert.Panics(
func() { m.serve(nil, &Request{}) },
"missing response writer",
)

var writerBuf bytes.Buffer
var logBuf bytes.Buffer
testLogger := hclog.New(&hclog.LoggerOptions{
Name: "TestServer_Run-logger",
Level: hclog.Debug,
Output: &logBuf,
})
w, err := newResponseWriter(bufio.NewWriter(&writerBuf), &sync.Mutex{}, testLogger, 1, 2)
require.NoError(err)
m.serve(w, nil)
assert.Contains(logBuf.String(), "missing request")
})
}
7 changes: 6 additions & 1 deletion packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ func (p *packet) requestMessageID() (int64, error) {
if err := msgIdPacket.assert(ber.ClassUniversal, ber.TypePrimitive, withTag(ber.TagInteger)); err != nil {
return 0, fmt.Errorf("%s: missing/invalid packet: %w", op, err)
}
return msgIdPacket.Value.(int64), nil
id, ok := msgIdPacket.Value.(int64)
if !ok {
return 0, fmt.Errorf("%s: expected int64 message ID and got %t: %w", op, msgIdPacket.Value, ErrInvalidParameter)
}
return id, nil
}

func (p *packet) requestPacket() (*packet, error) {
Expand Down Expand Up @@ -306,6 +310,7 @@ func (p *packet) assert(cl ber.Class, ty ber.Type, opt ...Option) error {
return nil
}

// Log will pretty print log a packet
func (p *packet) Log(out io.Writer, indent int, printBytes bool) {
indent_str := ""

Expand Down
Loading

0 comments on commit ad93cf2

Please sign in to comment.