internal/ldap: Implement search for entities
This commit is contained in:
parent
2b8ce28a83
commit
2cf2849cea
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
||||||
github.com/hashicorp/go-hclog v0.9.2
|
github.com/hashicorp/go-hclog v0.9.2
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
|
||||||
github.com/netauth/netauth v0.3.4
|
github.com/netauth/netauth v0.3.4
|
||||||
|
github.com/netauth/protocol v0.0.0-20191124005711-167b58b61c72
|
||||||
github.com/ps78674/goldap v0.0.0-20200721080011-cd2e7ee23841
|
github.com/ps78674/goldap v0.0.0-20200721080011-cd2e7ee23841
|
||||||
github.com/ps78674/ldapserver v0.0.0-20200521101606-2395f680392c
|
github.com/ps78674/ldapserver v0.0.0-20200521101606-2395f680392c
|
||||||
github.com/spf13/viper v1.3.2
|
github.com/spf13/viper v1.3.2
|
||||||
|
|
|
@ -17,20 +17,18 @@ func New(l hclog.Logger, nacl naClient) *server {
|
||||||
x.c = nacl
|
x.c = nacl
|
||||||
x.Server = ldap.NewServer()
|
x.Server = ldap.NewServer()
|
||||||
|
|
||||||
routes := ldap.NewRouteMux()
|
x.routes = ldap.NewRouteMux()
|
||||||
routes.NotFound(x.handleNotFound)
|
x.routes.NotFound(x.handleNotFound)
|
||||||
routes.Abandon(x.handleAbandon)
|
x.routes.Abandon(x.handleAbandon)
|
||||||
routes.Bind(x.handleBind)
|
x.routes.Bind(x.handleBind)
|
||||||
|
|
||||||
routes.Search(x.handleSearchDSE).
|
x.routes.Search(x.handleSearchDSE).
|
||||||
BaseDn("").
|
BaseDn("").
|
||||||
Scope(ldap.SearchRequestScopeBaseObject).
|
Scope(ldap.SearchRequestScopeBaseObject).
|
||||||
Filter("(objectclass=*)").
|
Filter("(objectclass=*)").
|
||||||
Label("Search - ROOT DSE")
|
Label("Search - ROOT DSE")
|
||||||
|
|
||||||
routes.Search(x.handleSearch).Label("Search - Generic")
|
x.Handle(x.routes)
|
||||||
|
|
||||||
x.Handle(routes)
|
|
||||||
|
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
@ -88,4 +86,12 @@ func (s *server) SetDomain(domain string) {
|
||||||
for i := range s.nc {
|
for i := range s.nc {
|
||||||
s.nc[i] = strings.TrimSpace(s.nc[i])
|
s.nc[i] = strings.TrimSpace(s.nc[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register routes that are dependent on the namingConvention
|
||||||
|
entitySearchDN := "ou=entities," + strings.Join(s.nc, ",")
|
||||||
|
s.routes.Search(s.handleSearchEntities).
|
||||||
|
BaseDn(entitySearchDN).
|
||||||
|
Scope(ldap.SearchRequestHomeSubtree).
|
||||||
|
Label("Search - Entities")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ps78674/goldap/message"
|
"github.com/ps78674/goldap/message"
|
||||||
ldap "github.com/ps78674/ldapserver"
|
ldap "github.com/ps78674/ldapserver"
|
||||||
|
|
||||||
"github.com/netauth/ldap/internal/buildinfo"
|
"github.com/netauth/ldap/internal/buildinfo"
|
||||||
|
|
||||||
|
pb "github.com/netauth/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) {
|
func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) {
|
||||||
|
@ -25,37 +29,101 @@ func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) {
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
func (s *server) handleSearchEntities(w ldap.ResponseWriter, m *ldap.Message) {
|
||||||
r := m.GetSearchRequest()
|
ctx := context.Background()
|
||||||
log.Printf("Request BaseDn=%s", r.BaseObject())
|
s.l.Debug("Search Entities")
|
||||||
log.Printf("Request Filter=%s", r.Filter())
|
|
||||||
log.Printf("Request FilterString=%s", r.FilterString())
|
|
||||||
log.Printf("Request Attributes=%s", r.Attributes())
|
|
||||||
log.Printf("Request TimeLimit=%d", r.TimeLimit().Int())
|
|
||||||
|
|
||||||
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
|
r := m.GetSearchRequest()
|
||||||
select {
|
|
||||||
case <-m.Done:
|
// This switch performs stage one of mapping from an ldap
|
||||||
log.Print("Leaving handleSearch...")
|
// search expression to a NetAuth search expression. The
|
||||||
return
|
// second phase of the mapping happens in another function.
|
||||||
|
var expr string
|
||||||
|
var err error
|
||||||
|
switch r.Filter().(type) {
|
||||||
|
case message.FilterEqualityMatch:
|
||||||
|
f := r.Filter().(message.FilterEqualityMatch)
|
||||||
|
expr, err = entitySearchExprHelper(string(f.AttributeDesc()), "=", string(f.AssertionValue()))
|
||||||
default:
|
default:
|
||||||
|
err = errors.New("unsupported filter type")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// If err is non-nil at this point it must mean that
|
||||||
|
// the above match didn't find a supported filter.
|
||||||
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultUnwillingToPerform)
|
||||||
|
res.SetDiagnosticMessage("Filter type not supported")
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e := ldap.NewSearchResultEntry("cn=Valere JEANTET, " + string(r.BaseObject()))
|
s.l.Debug("Searching entities", "query", expr)
|
||||||
e.AddAttribute("mail", "valere.jeantet@gmail.com", "mail@vjeantet.fr")
|
|
||||||
e.AddAttribute("company", "SODADI")
|
|
||||||
e.AddAttribute("department", "DSI/SEC")
|
|
||||||
e.AddAttribute("l", "Ferrieres en brie")
|
|
||||||
e.AddAttribute("mobile", "0612324567")
|
|
||||||
e.AddAttribute("telephoneNumber", "0612324567")
|
|
||||||
e.AddAttribute("cn", "Valère JEANTET")
|
|
||||||
w.Write(e)
|
|
||||||
|
|
||||||
e = ldap.NewSearchResultEntry("cn=Claire Thomas, " + string(r.BaseObject()))
|
ents, err := s.c.EntitySearch(ctx, expr)
|
||||||
e.AddAttribute("mail", "claire.thomas@gmail.com")
|
if err != nil {
|
||||||
e.AddAttribute("cn", "Claire THOMAS")
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError)
|
||||||
|
res.SetDiagnosticMessage(err.Error())
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ents {
|
||||||
|
e, err := s.entitySearchResult(ctx, ents[i], r.BaseObject(), r.Attributes())
|
||||||
|
if err != nil {
|
||||||
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError)
|
||||||
|
res.SetDiagnosticMessage(err.Error())
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Write(e)
|
w.Write(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.l.Debug("Entities", "res", ents)
|
||||||
|
|
||||||
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
|
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// entitySearchResult maps an entity onto a SearchResultEntry,
|
||||||
|
// performing the additional lookup for groups to populate the
|
||||||
|
// memberOf attribute. Though not implemented, the attrs list is
|
||||||
|
// plumbed down to this level to permit attribute filtering in the
|
||||||
|
// future.
|
||||||
|
func (s *server) entitySearchResult(ctx context.Context, e *pb.Entity, dn message.LDAPDN, attrs message.AttributeSelection) (message.SearchResultEntry, error) {
|
||||||
|
res := ldap.NewSearchResultEntry("uid=" + e.GetID() + "," + string(dn))
|
||||||
|
res.AddAttribute("uid", message.AttributeValue(e.GetID()))
|
||||||
|
res.AddAttribute("uidNumber", message.AttributeValue(strconv.Itoa(int(e.GetNumber()))))
|
||||||
|
|
||||||
|
grps, err := s.c.EntityGroups(ctx, e.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range grps {
|
||||||
|
g := "cn=" + grps[i].GetName() + ",ou=groups," + strings.Join(s.nc, ",")
|
||||||
|
res.AddAttribute("memberOf", message.AttributeValue(g))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// entitySearchExprHelper helps in mapping ldap search expressions to
|
||||||
|
// search expressions that NetAuth understands.
|
||||||
|
func entitySearchExprHelper(attr, op, val string) (string, error) {
|
||||||
|
var predicate, operator string
|
||||||
|
|
||||||
|
switch attr {
|
||||||
|
case "uid":
|
||||||
|
predicate = "ID"
|
||||||
|
default:
|
||||||
|
return "", errors.New("search attribute is unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case "=":
|
||||||
|
operator = "="
|
||||||
|
default:
|
||||||
|
return "", errors.New("search comparison is unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
return predicate + operator + val, nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,15 +5,21 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
ldap "github.com/ps78674/ldapserver"
|
ldap "github.com/ps78674/ldapserver"
|
||||||
|
|
||||||
|
pb "github.com/netauth/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
type naClient interface {
|
type naClient interface {
|
||||||
AuthEntity(context.Context, string, string) error
|
AuthEntity(context.Context, string, string) error
|
||||||
|
EntitySearch(context.Context, string) ([]*pb.Entity, error)
|
||||||
|
EntityGroups(context.Context, string) ([]*pb.Group, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
*ldap.Server
|
*ldap.Server
|
||||||
|
|
||||||
|
routes *ldap.RouteMux
|
||||||
|
|
||||||
c naClient
|
c naClient
|
||||||
l hclog.Logger
|
l hclog.Logger
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue