Documentation
¶
Overview ¶
Package activeldap is a pure-Go (no cgo) reimplementation of the ActiveRecord-style LDAP object-relational mapper of Ruby's ActiveLdap gem: the object↔entry mapping (ldap_mapping), the finder/search query builder, the dirty-tracking record with single- and multi-valued attributes, validations, belongs_to/has_many associations over distinguished names, and LDIF import/export. It mirrors ActiveLdap's semantics faithfully, independent of any Ruby runtime.
What it is — and isn't ¶
The judgement-and-string-building heart of ActiveLdap — turning a mapping plus a set of conditions into a distinguished name and an RFC 4515 search filter, tracking which attributes changed, deciding add-vs-modify on save, diffing dirty attributes into LDAP modify operations, validating, and serialising to LDIF — is fully deterministic and needs no directory server and no Ruby runtime, so it lives here as plain Go.
Talking to an actual directory is the host's job. A Base is bound to a Directory seam — the small four-method surface ActiveLdap uses out of Net::LDAP (search / add / modify / delete). The host (go-embedded-ruby's rbgo) wires that seam to the bound Net::LDAP connection provided by go-ruby-ldap; tests wire it to the in-memory MockDirectory. The mapping, DN, filter, dirty-diff and LDIF logic is what this library owns and tests; the network is the seam.
Mapping ¶
A model is described by a Mapping — the Go form of ActiveLdap's ldap_mapping(dn_attribute:, prefix:, classes:, scope:). NewClass compiles a mapping (plus attribute aliases and single-valued declarations) into a Class; Class.New and the finder methods (Class.Find, Class.Search, Class.Exist, Class.Create) mint and load Base records against a Connection.
Index ¶
- Constants
- func BuildDN(attr, value, prefix, base string) string
- func EscapeFilterValue(v string) string
- func ParseDNParent(normalized string) string
- type Base
- func (b *Base) Add(name string, values ...string)
- func (b *Base) Association(name string) ([]*Base, error)
- func (b *Base) AssociationOne(name string) (*Base, error)
- func (b *Base) AttributeNames() []string
- func (b *Base) Attributes() map[string][]string
- func (b *Base) Changed() bool
- func (b *Base) ChangedAttributes() []string
- func (b *Base) Changes() map[string][2][]string
- func (b *Base) Class() *Class
- func (b *Base) DN() string
- func (b *Base) Delete(name string)
- func (b *Base) Destroy() error
- func (b *Base) Errors() *Errors
- func (b *Base) Get(name string) []string
- func (b *Base) Has(name string) bool
- func (b *Base) ID() string
- func (b *Base) NewRecord() bool
- func (b *Base) One(name string) string
- func (b *Base) Persisted() bool
- func (b *Base) Reload() error
- func (b *Base) Save() error
- func (b *Base) SaveOK() bool
- func (b *Base) Set(name string, values ...string)
- func (b *Base) SetID(id string)
- func (b *Base) ToLDIF() string
- func (b *Base) ToS() string
- func (b *Base) UpdateAttributes(attrs map[string][]string) error
- func (b *Base) Valid() bool
- type Class
- func (c *Class) BaseDN() string
- func (c *Class) BelongsTo(name string, target *Class, foreignKey, primaryKey string)
- func (c *Class) Connection() *Connection
- func (c *Class) Create(attrs map[string][]string) (*Base, error)
- func (c *Class) DNForID(id string) string
- func (c *Class) Exist(id string) (bool, error)
- func (c *Class) Find(id string) (*Base, error)
- func (c *Class) FindAll(opts FindOptions) ([]*Base, error)
- func (c *Class) FindFirst(opts FindOptions) (*Base, error)
- func (c *Class) HasMany(name string, target *Class, foreignKey, primaryKey string)
- func (c *Class) LoadLDIF(ldif string) ([]*Base, error)
- func (c *Class) Mapping() *Mapping
- func (c *Class) Name() string
- func (c *Class) New() *Base
- func (c *Class) Search(opts FindOptions) ([]*Base, error)
- func (c *Class) Validate(v Validator)
- type Connection
- type DN
- type Directory
- type Entry
- type EntryNotFoundError
- type Errors
- type Filter
- func And(subs ...Filter) Filter
- func Conditions(conds map[string][]string) Filter
- func Equal(attr, value string) Filter
- func Not(sub Filter) Filter
- func Or(subs ...Filter) Filter
- func ParseFilter(s string) (Filter, error)
- func Present(attr string) Filter
- func RawFilter(text string) Filter
- func Substring(attr, initial string, anyParts []string, final string) Filter
- type FindOptions
- type LDIFRecord
- type Mapping
- type MockDirectory
- func (m *MockDirectory) Add(dn string, attributes map[string][]string) error
- func (m *MockDirectory) Delete(dn string) error
- func (m *MockDirectory) Modify(dn string, ops []ModifyOp) error
- func (m *MockDirectory) Search(req SearchRequest) ([]*Entry, error)
- func (m *MockDirectory) Seed(dn string, attrs map[string][]string)
- type ModOp
- type ModifyOp
- type RDN
- type Scope
- type SearchRequest
- type ValidationError
- type Validator
Constants ¶
const DNKey = "dn"
DNKey is the sentinel key meaning "the entry's distinguished name" in an association's foreign_key/primary_key — the ActiveLdap foreign_key: "dn" form.
const ErrorsBase = "base"
ErrorsBase is the pseudo-attribute under which record-level errors are filed.
Variables ¶
This section is empty.
Functions ¶
func BuildDN ¶
BuildDN composes a full DN from a leaf RDN (attribute=value), an optional prefix (an ActiveLdap "prefix" such as ou=Users, itself a DN), and a base DN. Empty prefix or base segments are skipped. The result is the ActiveLdap dn = "<dn_attribute>=<value>,<prefix>,<base>".
func EscapeFilterValue ¶
EscapeFilterValue escapes an assertion value for an RFC 4515 filter: the characters * ( ) \ and NUL become \2a \28 \29 \5c \00. Everything else is passed through, so a caller may embed a literal '*' only via Substring.
func ParseDNParent ¶
ParseDNParent returns the normalized parent DN of a normalized DN string.
Types ¶
type Base ¶
type Base struct {
// contains filtered or unexported fields
}
Base is a single LDAP entry as an ActiveRecord-style object — the instance side of the ORM, the Go counterpart of an ActiveLdap::Base instance. It holds the current attribute values, tracks which changed since it was loaded/saved (dirty tracking), knows its class (mapping + connection), and answers whether it is new or persisted. Mint one with Class.New or load one with the finder methods.
func (*Base) Add ¶
Add appends values to an attribute (a multi-valued convenience), preserving existing values.
func (*Base) Association ¶
Association resolves a named association from this record, returning every matching target record. An unknown name returns an error.
func (*Base) AssociationOne ¶
AssociationOne resolves a singular association (Class.BelongsTo) and returns the first matching record, or nil if none.
func (*Base) AttributeNames ¶
AttributeNames returns the canonical attribute names in assignment order.
func (*Base) Attributes ¶
Attributes returns a name→values snapshot of the record's attributes, the Go form of ActiveLdap's #attributes hash.
func (*Base) Changed ¶
Changed reports whether any attribute differs from the load/save baseline — ActiveLdap's #changed?. A new record is considered changed once it has any attribute beyond its objectClasses.
func (*Base) ChangedAttributes ¶
ChangedAttributes returns the folded names of attributes whose values differ from the baseline, sorted — the keys of ActiveLdap's #changes.
func (*Base) Changes ¶
Changes returns, per changed attribute, the [before, after] value pair — ActiveLdap's #changes.
func (*Base) DN ¶
DN returns the record's distinguished name: its explicit loaded DN if it has one, otherwise the DN computed from the dn_attribute value, prefix and base. It returns "" when a new record has no dn_attribute value yet.
func (*Base) Destroy ¶
Destroy deletes the record from the directory — ActiveLdap's #destroy. A new (never-saved) record cannot be destroyed and returns an error. On success the record becomes non-persisted.
func (*Base) Errors ¶
Errors returns the validation errors from the last Base.Valid call.
func (*Base) NewRecord ¶
NewRecord reports whether the record has not yet been saved (ActiveLdap's new_entry? / !persisted?).
func (*Base) One ¶
One returns the first value of an attribute, or "" — the natural accessor for a single-valued attribute.
func (*Base) Reload ¶
Reload re-reads the record from the directory, discarding unsaved changes and resetting dirty tracking — ActiveLdap's #reload. It errors if the entry no longer exists.
func (*Base) Save ¶
Save persists the record: an INSERT (Directory.Add) for a new record, or a diff-based UPDATE (Directory.Modify of only the changed attributes) for an existing one — ActiveLdap's #save. It validates first and returns a ValidationError without touching the directory when invalid. On success the record becomes persisted and its dirty baseline is reset.
func (*Base) SaveOK ¶
SaveOK is the boolean-returning save (ActiveLdap's #save returning true/false): it reports success and leaves the reason in Base.Errors / the returned error discarded.
func (*Base) Set ¶
Set assigns an attribute's values, replacing any existing ones, and records the change for dirty tracking. Passing no values clears the attribute.
func (*Base) ToLDIF ¶
ToLDIF renders the record as an RFC 2849 LDIF entry — ActiveLdap's #to_ldif. The dn: line comes first, then every attribute value on its own "name: value" line, in canonical-name order with the objectClass values first (as ActiveLdap emits them). A value needing it (leading space/colon/<, a non-ASCII or control byte) is base64-encoded on a "name:: b64" line.
func (*Base) UpdateAttributes ¶
UpdateAttributes assigns the given attributes and saves — ActiveLdap's #update_attributes. It returns the save error (a ValidationError on invalid input, leaving the in-memory changes in place as ActiveLdap does).
func (*Base) Valid ¶
Valid runs all validations and returns whether the record is error-free — ActiveLdap's #valid?. It clears prior errors first, then runs the structural checks (dn_attribute presence, mapped objectClasses present) followed by every registered custom validator, so Base.Errors reflects only the latest run.
type Class ¶
type Class struct {
// contains filtered or unexported fields
}
Class is a compiled model — a Mapping bound to a Connection, the Go counterpart of an ActiveLdap::Base subclass. Its methods (Class.New, Class.Find, Class.Search, Class.Exist, Class.Create) are the class-side ORM surface. Associations and validators are registered on it before use.
func NewClass ¶
func NewClass(name string, m *Mapping, conn *Connection) *Class
NewClass compiles a Mapping and binds it to a Connection, yielding a Class. The class name is used in error messages and to_s. It panics if the mapping omits its required DNAttribute, mirroring ActiveLdap raising on an incomplete ldap_mapping.
func (*Class) BaseDN ¶
BaseDN returns the DN of this class's container: the mapping prefix joined to the connection base — the parent under which every entry's DN is formed.
func (*Class) BelongsTo ¶
BelongsTo registers a belongs_to association: this record's foreignKey values reference the target's primaryKey. Resolving it yields the target record(s) whose primaryKey matches — the "parent" of this record. Use DNKey for a DN-valued reference (e.g. member: "dn").
func (*Class) Connection ¶
func (c *Class) Connection() *Connection
Connection returns the class's bound connection.
func (*Class) Create ¶
Create mints a record, assigns the given attributes, and saves it — the Go form of Model.create. The record is returned whether or not it saved; check the error (a ValidationError on invalid input) and Base.Persisted.
func (*Class) DNForID ¶
DNForID builds the full DN for a record whose dn_attribute value is id: "<dn_attribute>=<id>,<prefix>,<base>".
func (*Class) Exist ¶
Exist reports whether a record with the given dn_attribute value exists — ActiveLdap's exist?.
func (*Class) Find ¶
Find loads the single record whose dn_attribute equals id — find(id). It searches at ScopeBase on the record's computed DN, raising EntryNotFoundError when absent.
func (*Class) FindAll ¶
func (c *Class) FindAll(opts FindOptions) ([]*Base, error)
FindAll returns all records matching the options (find(:all, ...)).
func (*Class) FindFirst ¶
func (c *Class) FindFirst(opts FindOptions) (*Base, error)
FindFirst returns the first matching record, or nil if none — find(:first, ...).
func (*Class) HasMany ¶
HasMany registers a has_many association: target records carry a foreignKey referencing this record's primaryKey. Resolving it yields every target that points back — the "children" of this record.
func (*Class) LoadLDIF ¶
LoadLDIF parses an LDIF string and creates a record of this class for each entry, saving them through the connection — the Go form of ActiveLdap's LDIF import. It returns the created records; the first save error aborts and is returned with the records created so far.
func (*Class) New ¶
New mints a new, unsaved record of this class with the objectClass values from the mapping already set — the Go form of Model.new. Additional attributes are assigned afterwards with Base.Set.
func (*Class) Search ¶
func (c *Class) Search(opts FindOptions) ([]*Base, error)
Search runs a search and returns every matching record, the Go form of Model.search / find(:all). It applies the class objectClass guard, the given options, and (if set) the limit.
func (*Class) Validate ¶
Validate registers a custom validator on the class, run by Base.Valid after the built-in structural checks.
type Connection ¶
type Connection struct {
// Directory is the seam performing the actual LDAP operations.
Directory Directory
// Base is the connection's base DN (the base: of establish_connection),
// e.g. "dc=example,dc=com".
Base string
}
Connection is the bound directory a Class operates against — the Directory seam plus the base DN under which the class's prefix is resolved. It is the Go form of the state ActiveLdap::Base.setup_connection / establish_connection installs (host/port/bind live inside the Directory the host wires).
func NewConnection ¶
func NewConnection(dir Directory, base string) *Connection
NewConnection builds a Connection over a Directory and base DN.
type DN ¶
type DN struct{ RDNs []RDN }
DN is a parsed distinguished name — an ordered list of RDNs, most specific first, exactly as ActiveLdap's DN wraps a sequence of components.
func ParseDN ¶
ParseDN parses a distinguished name string into a DN. Unescaped commas separate RDNs and the first unescaped '=' splits each RDN; surrounding whitespace around a component is trimmed. An empty string parses to the empty (root) DN. A component with no '=' makes ParseDN return ok=false.
func (DN) Equal ¶
Equal reports whether two DNs denote the same entry, comparing case- and whitespace-insensitively via DN.Normalized.
func (DN) Normalized ¶
Normalized returns a canonical, case-folded form used for DN equality: each attribute name is lower-cased and each value is lower-cased and whitespace-collapsed. ActiveLdap compares DNs case-insensitively, so two DNs are "equal" when their Normalized strings match.
type Directory ¶
type Directory interface {
Search(req SearchRequest) ([]*Entry, error)
Add(dn string, attributes map[string][]string) error
Modify(dn string, ops []ModifyOp) error
Delete(dn string) error
}
Directory is the seam between this ORM and a real LDAP server: the four Net::LDAP operations ActiveLdap uses. The host (rbgo) wires it to a bound Net::LDAP connection; tests wire it to MockDirectory. Every method returns an error the ORM surfaces as a save/find failure.
type Entry ¶
Entry is a directory entry as returned by a search — a DN plus its attributes. It mirrors a Net::LDAP::Entry: attribute names are case-insensitive and values are always a list.
type EntryNotFoundError ¶
EntryNotFoundError is returned by Class.Find when no entry has the requested dn_attribute value — ActiveLdap's EntryNotFound.
func (*EntryNotFoundError) Error ¶
func (e *EntryNotFoundError) Error() string
type Errors ¶
type Errors struct {
// contains filtered or unexported fields
}
Errors collects validation messages per attribute, the Go form of ActiveLdap/ActiveModel's errors object. The special key ErrorsBase holds record-level messages not tied to one attribute.
func (*Errors) Add ¶
Add files a message against an attribute (use ErrorsBase for record-level).
func (*Errors) Empty ¶
Empty reports whether there are no errors — the negation of ActiveModel's #any?.
func (*Errors) FullMessages ¶
FullMessages returns every message prefixed by its attribute, in insertion order — ActiveModel's #full_messages. Record-level (ErrorsBase) messages are emitted unprefixed.
type Filter ¶
type Filter interface {
String() string
// contains filtered or unexported methods
}
Filter is an RFC 4515 LDAP search filter, the value ActiveLdap builds from a find/search :filter option (a String is used verbatim; a Hash of conditions is AND-combined) and passes to Net::LDAP. Filters compose with And, Or and Not; leaves are built with Equal, Present and Substring. String renders the parenthesised text.
func And ¶
And builds a conjunction (&...). With a single sub-filter it returns that sub-filter unchanged (no redundant &-wrapper), matching ActiveLdap's filter simplification; with none it returns the "match everything" objectClass=* present filter is left to callers — And of nothing renders as an empty group.
func Conditions ¶
Conditions builds the Filter for a Hash-style :filter option — a map of attribute→values AND-combined as equalities, the common ActiveLdap find(filter: {uid: "alice"}) form. It returns nil for an empty map.
func ParseFilter ¶
ParseFilter parses an RFC 4515 filter string into a Filter tree. It handles the operators & | ! and the leaves attr=value, attr=* (present) and substring (attr=a*b*c), unescaping \\XX and \\c in assertion values. It is the inverse of Filter.String for the subset this ORM emits, letting MockDirectory evaluate the very filters the query builder produces. A malformed filter returns an error.
type FindOptions ¶
type FindOptions struct {
// Filter is an extra condition AND-combined with the objectClass guard. It
// may be any [Filter] (including one built from a Hash of conditions via
// [Conditions]); nil adds no extra condition.
Filter Filter
// Base overrides the search base DN (the base: option). Empty uses the
// class base DN.
Base string
// Scope overrides the search scope; nil uses the mapping scope.
Scope *Scope
// Attributes limits the returned attributes; nil returns all.
Attributes []string
// Limit caps the number of records returned when > 0 (ActiveLdap's :limit).
Limit int
}
FindOptions are the keyword options of ActiveLdap's find/search:
find(:all, filter: "(mail=*)", base: "ou=People,dc=x", scope: :one, attributes: [...])
A zero FindOptions means "the class defaults": the mapping scope, the class base DN, the objectClass guard as the only filter, and all attributes.
type LDIFRecord ¶
LDIFRecord is one entry parsed from an LDIF stream: its DN and attributes.
func ParseLDIF ¶
func ParseLDIF(s string) ([]LDIFRecord, error)
ParseLDIF parses an RFC 2849 LDIF string into records. It handles line folding (a continuation line begins with a single space), comments (# …), blank-line record separators, and both "name: value" and base64 "name:: value" forms. A record missing its dn: line is an error.
type Mapping ¶
type Mapping struct {
// DNAttribute is the RDN attribute of every entry of this class (the
// dn_attribute:), e.g. "uid" or "cn". Required.
DNAttribute string
// Prefix is the container DN, relative to the connection base, under which
// entries live (the prefix:), e.g. "ou=Users". May be empty.
Prefix string
// Classes are the objectClass values every entry of this model carries
// (classes:). The first is conventionally the structural class. Required.
Classes []string
// Scope is the default search scope for finds (scope:); the zero value
// [ScopeSub] matches ActiveLdap's default of :sub.
Scope Scope
// Aliases maps an alternative attribute name to its canonical name
// (ActiveLdap attribute aliases), e.g. {"commonName": "cn"}. Case-insensitive.
Aliases map[string]string
// SingleValued lists attributes presented as a single scalar rather than a
// list (schema SINGLE-VALUE), so [Base.One] is the natural accessor.
SingleValued []string
}
Mapping is the Go form of ActiveLdap's ldap_mapping declaration — how a model class maps to a region of the directory:
ldap_mapping dn_attribute: "uid", prefix: "ou=Users",
classes: ["top", "person", "inetOrgPerson"], scope: :sub
The zero Mapping is invalid; build one and pass it to NewClass.
type MockDirectory ¶
type MockDirectory struct {
// Log records every mutating call in order, for assertions (mirrors the
// Net::LDAP mock's operation log).
Log []string
// FailOn, when set, makes the named operation ("add"/"modify"/"delete"/
// "search") return an error, to exercise the ORM's error branches.
FailOn map[string]string
// contains filtered or unexported fields
}
MockDirectory is an in-memory Directory — the ActiveLdap test double and the fallback the rbgo binding uses when no Net::LDAP connection is configured. It stores entries by their normalized DN and answers searches by scope and filter, so the whole ORM can be exercised with no server.
func NewMockDirectory ¶
func NewMockDirectory() *MockDirectory
NewMockDirectory builds an empty MockDirectory.
func (*MockDirectory) Add ¶
func (m *MockDirectory) Add(dn string, attributes map[string][]string) error
Add implements Directory.Add.
func (*MockDirectory) Delete ¶
func (m *MockDirectory) Delete(dn string) error
Delete implements Directory.Delete.
func (*MockDirectory) Modify ¶
func (m *MockDirectory) Modify(dn string, ops []ModifyOp) error
Modify implements Directory.Modify, applying add/replace/delete ops.
func (*MockDirectory) Search ¶
func (m *MockDirectory) Search(req SearchRequest) ([]*Entry, error)
Search implements Directory.Search over the in-memory store.
type ModOp ¶
type ModOp int
ModOp is the kind of a ModifyOp — the Net::LDAP modify operation symbol.
type ModifyOp ¶
ModifyOp is one element of a Net::LDAP modify operations list — [op, attribute, values].
type RDN ¶
RDN is a single relative distinguished name component — one attribute=value pair such as cn=Alice. Multi-valued RDNs (cn=Alice+uid=alice) are not part of the small model ActiveLdap's dn_attribute mapping produces, so an RDN holds exactly one pair.
type Scope ¶
type Scope int
Scope is an LDAP search scope, the ActiveLdap :scope option and the Net::LDAP scope constant it maps to.
func ParseScope ¶
ParseScope maps an ActiveLdap scope symbol name to a Scope. It accepts "base"/"baseobject", "one"/"onelevel"/"singlelevel", "sub"/"subtree"/ "wholesubtree"; an unknown name returns ok=false.
type SearchRequest ¶
SearchRequest is the argument to Directory.Search, the four fields ActiveLdap fills from a find/search: the base DN, the scope, the RFC 4515 filter string, and the attributes to return (nil = all).
type ValidationError ¶
ValidationError is the error a save returns when validation fails; its message is the joined full messages.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
type Validator ¶
type Validator func(b *Base)
Validator inspects a record and files any problems on its Errors. Register one with Class.Validate; the built-ins (PresenceOf, RequiredClasses and the mapping-derived DN/objectClass checks) are Validators too.
func PresenceOf ¶
PresenceOf returns a validator asserting each named attribute has at least one non-blank value — ActiveLdap's validates_presence_of.
func RequiredClasses ¶
RequiredClasses returns a validator asserting the record carries each named objectClass — the check ActiveLdap derives from a mapping's required classes.
