diff --git a/internal/ldap/ldap.go b/internal/ldap/ldap.go index 404dcdb..caf1563 100644 --- a/internal/ldap/ldap.go +++ b/internal/ldap/ldap.go @@ -94,4 +94,10 @@ func (s *server) SetDomain(domain string) { Scope(ldap.SearchRequestHomeSubtree). Label("Search - Entities") + groupSearchDN := "ou=groups," + strings.Join(s.nc, ",") + s.routes.Search(s.handleSearchGroups). + BaseDn(groupSearchDN). + Scope(ldap.SearchRequestHomeSubtree). + Label("Search - Entities") + } diff --git a/internal/ldap/search.go b/internal/ldap/search.go index 78889a9..99e609d 100644 --- a/internal/ldap/search.go +++ b/internal/ldap/search.go @@ -127,3 +127,99 @@ func entitySearchExprHelper(attr, op, val string) (string, error) { return predicate + operator + val, nil } + +func (s *server) handleSearchGroups(w ldap.ResponseWriter, m *ldap.Message) { + ctx := context.Background() + s.l.Debug("Search Groups") + + r := m.GetSearchRequest() + + // This switch performs stage one of mapping from an ldap + // search expression to a NetAuth search expression. The + // 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 = groupSearchExprHelper(string(f.AttributeDesc()), "=", string(f.AssertionValue())) + 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 + } + + s.l.Debug("Searching groups", "expr", expr) + + members, err := s.c.GroupSearch(ctx, expr) + if err != nil { + res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError) + res.SetDiagnosticMessage(err.Error()) + w.Write(res) + return + } + + for i := range members { + e, err := s.groupSearchResult(ctx, members[i], r.BaseObject(), r.Attributes()) + if err != nil { + res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError) + res.SetDiagnosticMessage(err.Error()) + w.Write(res) + return + } + w.Write(e) + } + + res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) + w.Write(res) +} + +// groupSearchResult maps a group onto a SearchResultEntry, performing +// the additional lookup for groups to populate the member attribute. +// Though not implemented, the attrs list is plumbed down to this +// level to permit attribute filtering in the future. +func (s *server) groupSearchResult(ctx context.Context, g *pb.Group, dn message.LDAPDN, attrs message.AttributeSelection) (message.SearchResultEntry, error) { + res := ldap.NewSearchResultEntry("cn=" + g.GetName() + "," + string(dn)) + res.AddAttribute("cn", message.AttributeValue(g.GetName())) + res.AddAttribute("gidNumber", message.AttributeValue(strconv.Itoa(int(g.GetNumber())))) + + members, err := s.c.GroupMembers(ctx, g.GetName()) + if err != nil { + return res, err + } + + for i := range members { + g := "uid=" + members[i].GetID() + ",ou=entities," + strings.Join(s.nc, ",") + res.AddAttribute("member", message.AttributeValue(g)) + } + + return res, nil +} + +// groupSearchExprHelper helps in mapping ldap search expressions to +// search expressions that NetAuth understands. +func groupSearchExprHelper(attr, op, val string) (string, error) { + var predicate, operator string + + switch attr { + case "cn": + predicate = "name" + 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 +} diff --git a/internal/ldap/type.go b/internal/ldap/type.go index 49cbbe3..486b9d4 100644 --- a/internal/ldap/type.go +++ b/internal/ldap/type.go @@ -13,6 +13,9 @@ type naClient interface { AuthEntity(context.Context, string, string) error EntitySearch(context.Context, string) ([]*pb.Entity, error) EntityGroups(context.Context, string) ([]*pb.Group, error) + + GroupSearch(context.Context, string) ([]*pb.Group, error) + GroupMembers(context.Context, string) ([]*pb.Entity, error) } type server struct {