Documentation
¶
Overview ¶
Package ldaptest is an LDAP directory to test against.
d := ldaptest.NewServer(&ldaptest.Directory{
People: map[string]ldaptest.Person{
"dora": {Password: "hunter2", NTHash: hex.EncodeToString(directory.NTHashOf("hunter2"))},
"eli": {Password: "swordfish"}, // a bind, and nothing else
},
Groups: map[string][]string{"engineers": {"dora", "eli"}},
})
defer d.Close()
src, err := ldapdir.New(ldapdir.Config{URL: d.URL, BaseDN: d.BaseDN, …})
It answers Bind and Search with glauth/ldap -- an independent implementation of the protocol, which is the point. A fake built from one reading of the specification can only ever confirm that reading: if the reading is wrong, the fake is wrong the same way and the test passes.
⛔ That is why this does NOT use go-authn/ldap, even though go-authn now has an LDAP server of its own and every other consumer has moved to it. ldapdir is a CLIENT. Testing it against a server from the same hands makes judge and subject one thing, and a shared misreading of RFC 4511 would pass both ways. An outside implementation is the only thing here that can disagree with us.
It exists because three packages had written the same fixture, and one of them is a file server in another organisation entirely. A fixture copied three times is three fixtures that drift.
Why it is deliberately permissive ¶
An empty password binds SUCCESSFULLY here, and that is a choice about TESTING rather than a copy of what a directory does.
⛔ The comment here used to say it succeeds "because it does in a real directory". It does not. RFC 4513 §5.1.2 is the unauthenticated bind -- a NAME with a zero-length password -- and it says servers "SHOULD by default fail Unauthenticated Bind requests with a resultCode of unwillingToPerform". (§5.1.1 is the ANONYMOUS bind, an empty name AND an empty password, which is legitimate and succeeds. The two are one field apart, and conflating them is how a directory either locks out every anonymous client or lets anybody in as anybody.)
The permissiveness is still right, for the opposite reason to the one that was written down: a server that treats a bind as proof must refuse an empty password ITSELF, and cannot be shown to do so against a fixture that quietly refuses it first. The fixture says yes so that the code under test has to say no.
Index ¶
- Variables
- type Directory
- type Person
- type Server
- func (s *Server) Bind(bindDN, password string, _ net.Conn) (server.LDAPResultCode, error)
- func (s *Server) Binds() int
- func (s *Server) Close() error
- func (s *Server) DN(uid string) string
- func (s *Server) FailBindsWith(code uint16)
- func (s *Server) Search(_ string, req server.SearchRequest, _ net.Conn) (server.ServerSearchResult, error)
Constants ¶
This section is empty.
Variables ¶
var DefaultObjectClasses = []string{"top", "account", "posixAccount"}
DefaultObjectClasses are what a Person is published with when it names none: the shape RFC 2307 §5.3 recommends for a posixAccount ("the account object class provides a convenient structural class").
Functions ¶
This section is empty.
Types ¶
type Directory ¶
type Directory struct {
// People, by the value of the uid attribute.
People map[string]Person
// Groups, by cn, holding uids.
Groups map[string][]string
// GroupExtra are any other attributes of a group in Groups, by the
// group's cn: {"staff": {"gidNumber": {"100"}}}.
GroupExtra map[string]map[string][]string
// GroupsOfNames are groupOfNames groups (RFC 4519 §3.5), by cn, holding
// DNs in member: any DNs, so that a test can put in one that names
// nobody, or somebody elsewhere.
GroupsOfNames map[string][]string
// BaseDN defaults to dc=example,dc=org, with people under
// ou=people and groups under ou=groups.
BaseDN string
// ReaderDN and ReaderPassword are the service account a client binds as
// to read. They default to cn=reader,<BaseDN> and "let me read".
ReaderDN string
ReaderPassword string
}
A Directory is what the server answers with.
type Person ¶
type Person struct {
// DN defaults to uid=<name>,<BaseDN>.
DN string
// Password is what a bind as this person accepts. Empty means no bind
// succeeds -- except the unauthenticated one, which succeeds for anybody.
Password string
// NTHash is sambaNTPassword, in the 32 hex characters a directory
// publishes. Use directory.NTHashOf and encoding/hex.
NTHash string
// SSHKeys are sshPublicKey values, in authorized_keys spelling.
SSHKeys []string
// TOTPSecret is a one-time-code secret in base32, published under
// whatever attribute name the test asks for -- there is no standard one.
// It goes out under "oathSecret" unless Extra says otherwise.
TOTPSecret string
// Extra are any other attributes, for a schema that names things
// differently.
Extra map[string][]string
// ObjectClasses are the entry's objectClass values. Nil is
// DefaultObjectClasses, a posixAccount on an account; a person that must
// NOT be a posixAccount says so here.
ObjectClasses []string
}
A Person is one entry: the credentials a directory could publish for them.
The distinction the whole thing exists for: a person with a Password can be BOUND as, and a person with an NTHash can be served over NTLMv2. A directory that publishes only the first cannot serve SMB, however good it is.
type Server ¶
type Server struct {
// URL is what to give a client: ldap://127.0.0.1:<port>.
URL string
// BaseDN, PeopleDN and GroupsDN are where the entries are, so a test does
// not repeat the strings this package chose.
BaseDN, PeopleDN, GroupsDN string
// ReaderDN and ReaderPassword are the service account that may read.
ReaderDN, ReaderPassword string
// contains filtered or unexported fields
}
A Server is a running directory. Close it when the test ends.
func (*Server) FailBindsWith ¶ added in v0.17.0
FailBindsWith makes every bind except the reader's answer with an LDAP result code (RFC 4511 §4.1.9), whatever the password: busy (51) and unavailable (52) for a directory that is failing, invalidCredentials (49) for one that is saying no. Zero restores the normal answers.