RFC-driven LDAPv3 client library for MoonBit with a generic ASN.1 BER codec
Dependencies
| Path | Purpose |
|---|---|
| *.mbt (module root) | Protocol core: BER, messages, filter, DN, session, client |
| socket/ | Native async/socket transport adapter |
| cmd/main | moonldap CLI (search / explain) |
| examples/ | Runnable examples (bind_search, sasl_plain, ...) |
| testdata/ | Scripted scenario data |
///|
async fn main {
let cfg = @moonldap.LdapConfig::new("localhost", 389)
let transport = @socket.SocketTransport::new()
let client = @moonldap.LdapClient::new(cfg, transport)
let _ = client.connect_and_bind_simple("cn=admin,dc=example,dc=com", "secret")
let req = @moonldap.SearchRequest::new(
"dc=example,dc=com",
@moonldap.SearchScope::Subtree,
"(cn=John*)",
).unwrap()
let outcome = client.search_to_array(req).unwrap()
for entry in outcome.entries {
println(entry.object_name)
}
println(client.trace.to_string())
}moon run cmd/main -- search --host localhost --port 389 \
--base "ou=People,dc=example,dc=com" --scope sub \
--filter "(cn=John*)" --attrs cn,mail --auth-dn "..." --auth-pass "..."moon run cmd/main -- explain testdata/scenario_bind.jsonmoon run examples/filter_roundtrip # filter string -> BER -> roundtrip
moon run examples/ber_dump # byte stream -> BER tree
moon run examples/fake_session # offline FakeTransport session
moon run examples/bind_search # live bind + search (needs a server)
moon run examples/sasl_plain # live SASL PLAIN bind (needs a server)pub(open) trait LdapTransport {
async fn connect(Self, LdapConfig) -> Result[Unit, LdapError]
async fn write(Self, Bytes) -> Result[Unit, LdapError]
async fn read(Self) -> Result[Bytes, LdapError]
fn close(Self) -> Unit
}fn AttributeValueAssertion::new(attribute_desc : String, assertion_value : Bytes) -> AttributeValueAssertionpub struct BindRequest {
version : Int
name : String
authentication : AuthenticationChoice
} derive(Eq, Debug)pub struct ExtendedResponse {
result : LdapResult
response_name : String?
response_value : Bytes?
} derive(Eq, Debug)fn ExtensibleMatch::new(matching_rule : String?, attr_type : String?, match_value : Bytes, dn_attributes : Bool) -> ExtensibleMatchimpl LdapTransport for FakeTransportasync fn[T : LdapTransport] LdapClient::abandon(self : LdapClient[T], message_id : Int) -> Result[Unit, LdapError]async fn[T : LdapTransport] LdapClient::add(self : LdapClient[T], request : AddRequest) -> Result[AddResponse, LdapError]async fn[T : LdapTransport] LdapClient::bind_plain(self : LdapClient[T], authzid : String, authcid : String, password : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] LdapClient::bind_simple(self : LdapClient[T], name : String, password : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] LdapClient::compare(self : LdapClient[T], request : CompareRequest) -> Result[CompareResponse, LdapError]async fn[T : LdapTransport] LdapClient::connect_and_bind_plain(self : LdapClient[T], authzid : String, authcid : String, password : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] LdapClient::connect_and_bind_simple(self : LdapClient[T], name : String, password : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] LdapClient::delete(self : LdapClient[T], entry : String) -> Result[DelResponse, LdapError]async fn[T : LdapTransport] LdapClient::modify(self : LdapClient[T], request : ModifyRequest) -> Result[ModifyResponse, LdapError]async fn[T : LdapTransport] LdapClient::modify_dn(self : LdapClient[T], request : ModifyDnRequest) -> Result[ModifyDnResponse, LdapError]async fn[T : LdapTransport] LdapClient::search(self : LdapClient[T], request : SearchRequest, on_entry? : (SearchResultEntry) -> Unit, on_reference? : (SearchResultReference) -> Unit) -> Result[LdapResult, LdapError]async fn[T : LdapTransport] LdapClient::search_paged(self : LdapClient[T], request : SearchRequest, page_size : Int) -> Result[SearchOutcome, LdapError]async fn[T : LdapTransport] LdapClient::search_to_array(self : LdapClient[T], request : SearchRequest) -> Result[SearchOutcome, LdapError]async fn[T : LdapTransport] LdapClient::start_tls(self : LdapClient[T]) -> Result[ExtendedResponse, LdapError]fn LdapConfig::new(host : String, port : Int, version? : Int, max_message_size? : Int, timeout_ms? : Int, require_tls? : Bool) -> LdapConfigpub(all) enum LdapFilter {
And(Array[LdapFilter])
Or(Array[LdapFilter])
Not(LdapFilter)
Equality(String, Bytes)
Substrings(String, SubstringFilter)
GreaterOrEqual(String, Bytes)
LessOrEqual(String, Bytes)
Present(String)
Approx(String, Bytes)
Extensible(ExtensibleMatch)
} derive(Eq, Debug)pub struct LdapMessage {
message_id : Int
op : ProtocolOp
controls : Array[Control]?
} derive(Eq, Debug)fn LdapMessage::with_controls(message_id : Int, op : ProtocolOp, controls : Array[Control]) -> LdapMessagepub struct LdapResult {
result_code : ResultCode
matched_dn : String
diagnostic_message : String
referral : Array[String]?
} derive(Eq, Debug)pub struct LdapUrl {
secure : Bool
host : String
port : Int
base_dn : String
attributes : Array[String]
scope : SearchScope
filter : String
} derive(Eq, Debug)fn ModifyDnRequest::new(entry : String, newrdn : String, delete_old_rdn : Bool, new_superior : String?) -> ModifyDnRequestpub struct ModifyRequestChange {
operation : ModifyOperation
modification : PartialAttribute
} derive(Eq, Debug)fn ModifyRequestChange::new(operation : ModifyOperation, modification : PartialAttribute) -> ModifyRequestChangepub(all) enum ProtocolOp {
BindRequest(BindRequest)
BindResponse(BindResponse)
UnbindRequest
SearchRequest(SearchRequest)
SearchResultEntry(SearchResultEntry)
SearchResultDone(LdapResult)
SearchResultReference(SearchResultReference)
ModifyRequest(ModifyRequest)
ModifyResponse(ModifyResponse)
AddRequest(AddRequest)
AddResponse(AddResponse)
DelRequest(DelRequest)
DelResponse(DelResponse)
ModifyDnRequest(ModifyDnRequest)
ModifyDnResponse(ModifyDnResponse)
CompareRequest(CompareRequest)
CompareResponse(CompareResponse)
ExtendedRequest(ExtendedRequest)
ExtendedResponse(ExtendedResponse)
AbandonRequest(Int)
} derive(Eq, Debug)pub(all) enum ResultCode {
Success
OperationsError
ProtocolError
TimeLimitExceeded
SizeLimitExceeded
CompareFalse
CompareTrue
AuthMethodNotSupported
StrongerAuthRequired
Referral
AdminLimitExceeded
UnavailableCriticalExtension
ConfidentialityRequired
SaslBindInProgress
NoSuchAttribute
UndefinedAttributeType
InappropriateMatching
ConstraintViolation
AttributeOrValueExists
InvalidAttributeSyntax
NoSuchObject
AliasProblem
InvalidDNSyntax
AliasDereferencingProblem
InappropriateAuthentication
InvalidCredentials
InsufficientAccessRights
Busy
Unavailable
UnwillingToPerform
LoopDetect
NamingViolation
ObjectClassViolation
NotAllowedOnNonLeaf
NotAllowedOnRDN
EntryAlreadyExists
ObjectClassModsProhibited
AffectsMultipleDSAs
Other
ServerDown
LocalError
EncodingError
DecodingError
Timeout
AuthUnknown
FilterError
UserCanceled
ParameterError
NoMemory
ConnectError
NotSupported
ControlNotFound
NoResultsReturned
MoreResultsToReturn
ClientLoop
ReferralLimitExceeded
Unknown(Int)
} derive(Eq, Debug)pub struct SearchOutcome {
entries : Array[SearchResultEntry]
references : Array[SearchResultReference]
result : LdapResult
} derive(Eq, Debug)pub struct SearchRequest {
base_object : String
scope : SearchScope
deref_aliases : DerefAliases
size_limit : Int
time_limit : Int
types_only : Bool
attributes : Array[String]
filter : LdapFilter
} derive(Eq, Debug)fn SearchRequest::new(base_object : String, scope : SearchScope, filter : String, deref_aliases? : DerefAliases, size_limit? : Int, time_limit? : Int, types_only? : Bool, attributes? : Array[String]) -> Result[SearchRequest, LdapError]pub struct SearchResultEntry {
object_name : String
attributes : Array[PartialAttribute]
} derive(Eq, Debug)fn SearchResultEntry::new(object_name : String, attributes : Array[PartialAttribute]) -> SearchResultEntryasync fn[T : LdapTransport] Session::abandon(self : Session[T], message_id : Int) -> Result[Unit, LdapError]async fn[T : LdapTransport] Session::add(self : Session[T], request : AddRequest) -> Result[AddResponse, LdapError]async fn[T : LdapTransport] Session::bind(self : Session[T], request : BindRequest) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] Session::bind_digest_md5(self : Session[T], username : String, password : String, digest_uri? : String, cnonce? : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] Session::bind_plain(self : Session[T], authzid : String, authcid : String, password : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] Session::bind_simple(self : Session[T], name : String, password : String) -> Result[BindResponse, LdapError]async fn[T : LdapTransport] Session::compare(self : Session[T], request : CompareRequest) -> Result[CompareResponse, LdapError]async fn[T : LdapTransport] Session::delete(self : Session[T], entry : String) -> Result[DelResponse, LdapError]async fn[T : LdapTransport] Session::modify(self : Session[T], request : ModifyRequest) -> Result[ModifyResponse, LdapError]async fn[T : LdapTransport] Session::modify_dn(self : Session[T], request : ModifyDnRequest) -> Result[ModifyDnResponse, LdapError]async fn[T : LdapTransport] Session::search(self : Session[T], request : SearchRequest, on_entry? : (SearchResultEntry) -> Unit, on_reference? : (SearchResultReference) -> Unit) -> Result[LdapResult, LdapError]async fn[T : LdapTransport] Session::search_done(self : Session[T], request : SearchRequest, controls? : Array[Control], on_entry? : (SearchResultEntry) -> Unit, on_reference? : (SearchResultReference) -> Unit) -> Result[SearchDone, LdapError]async fn[T : LdapTransport] Session::search_to_array(self : Session[T], request : SearchRequest) -> Result[SearchOutcome, LdapError]async fn[T : LdapTransport] Session::start_tls(self : Session[T]) -> Result[ExtendedResponse, LdapError]let SORT_REQUEST_OID : Stringlet SORT_RESPONSE_OID : Stringlet STARTTLS_OID : Stringfn compute_digest_md5_response(username~ : String, password~ : String, realm~ : String, nonce~ : String, cnonce~ : String, nc~ : String, qop~ : String, digest_uri~ : String) -> Stringfn encode_bit_string(data : Bytes, unused_bits : Int) -> Bytesfn encode_boolean(b : Bool) -> Bytesfn encode_enumerated(v : Int) -> Bytesfn encode_integer(v : Int) -> Bytesfn encode_length(len : Int) -> Bytesfn encode_octet_string(content : Bytes) -> Bytesfn encode_utf8_string(s : String) -> Bytesfn escape_dn_value(value : String) -> Stringfn escape_filter_value(value : Bytes) -> Stringfn from_hex(s : String) -> Result[Bytes, String]fn md5_hex(data : Bytes) -> Stringfn paged_results_request_value(size : Int, cookie : Bytes) -> Bytesfn paged_results_response_value(total_size : Int, cookie : Bytes) -> Bytesfn redact_password(_password : String) -> Stringfn redact_sasl_credentials(mechanism : String, payload : Bytes) -> Stringfn redact_sasl_payload(payload : Bytes) -> Bytesfn redact_secret(value : String) -> Stringfn sasl_plain_bind_request(name : String, authzid : String, authcid : String, password : String, version? : Int) -> BindRequestfn sort_response_value(sort_result : Int, attribute_type : String?) -> Bytesfn to_hex_lower(data : BytesView) -> Stringfn to_hex_upper(data : BytesView) -> StringRFC-driven LDAPv3 client library for MoonBit with a generic ASN.1 BER codec
Dependencies