///|
/// A high-level LDAP client that wraps a `Session` and records a diagnostic
/// trace. It never logs passwords or SASL payloads.
pub struct LdapClient[T] {
session : Session[T]
trace : LdapTrace
}
///|
pub fn[T] LdapClient::new(config : LdapConfig, transport : T) -> LdapClient[T] {
{ session: Session::new(config, transport), trace: LdapTrace::new() }
}
///|
pub fn[T] LdapClient::is_bound(self : LdapClient[T]) -> Bool {
self.session.is_bound()
}
///|
pub async fn[T : LdapTransport] LdapClient::connect(
self : LdapClient[T],
) -> Result[Unit, LdapError] {
self.session.connect()
}
///|
/// Connect, then perform a simple bind.
pub async fn[T : LdapTransport] LdapClient::connect_and_bind_simple(
self : LdapClient[T],
name : String,
password : String,
) -> Result[BindResponse, LdapError] {
match self.session.connect() {
Ok(_) => ()
Err(e) => return Err(e)
}
self.bind_simple(name, password)
}
///|
/// Connect, then perform a SASL PLAIN bind.
pub async fn[T : LdapTransport] LdapClient::connect_and_bind_plain(
self : LdapClient[T],
authzid : String,
authcid : String,
password : String,
) -> Result[BindResponse, LdapError] {
match self.session.connect() {
Ok(_) => ()
Err(e) => return Err(e)
}
self.bind_plain(authzid, authcid, password)
}
///|
/// Perform a simple bind and record the trace.
pub async fn[T : LdapTransport] LdapClient::bind_simple(
self : LdapClient[T],
name : String,
password : String,
) -> Result[BindResponse, LdapError] {
let response = match self.session.bind_simple(name, password) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_bind(response)
Ok(response)
}
///|
/// Perform a SASL PLAIN bind and record the trace.
pub async fn[T : LdapTransport] LdapClient::bind_plain(
self : LdapClient[T],
authzid : String,
authcid : String,
password : String,
) -> Result[BindResponse, LdapError] {
let response = match self.session.bind_plain(authzid, authcid, password) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_bind(response)
Ok(response)
}
///|
fn[T] LdapClient::record_bind(
self : LdapClient[T],
response : BindResponse,
) -> Unit {
let step = LdapStep::new(
"bind",
response.result.result_code.to_int(),
response.result.diagnostic_message,
)
self.trace.record_step(step)
self.trace.record_result(response.result)
}
///|
/// Run a streaming search and record the trace.
pub async fn[T : LdapTransport] LdapClient::search(
self : LdapClient[T],
request : SearchRequest,
on_entry? : (SearchResultEntry) -> Unit,
on_reference? : (SearchResultReference) -> Unit,
) -> Result[LdapResult, LdapError] {
let result = match (on_entry, on_reference) {
(Some(e), Some(r)) =>
self.session.search(request, on_entry=e, on_reference=r)
(Some(e), None) => self.session.search(request, on_entry=e)
(None, Some(r)) => self.session.search(request, on_reference=r)
(None, None) => self.session.search(request)
}
match result {
Ok(r) => {
self.record_search_result(r)
Ok(r)
}
Err(e) => Err(e)
}
}
///|
/// Collect a search into an outcome and record the trace.
pub async fn[T : LdapTransport] LdapClient::search_to_array(
self : LdapClient[T],
request : SearchRequest,
) -> Result[SearchOutcome, LdapError] {
let outcome = match self.session.search_to_array(request) {
Ok(o) => o
Err(e) => return Err(e)
}
self.trace.entries = outcome.entries.length()
self.record_search_result(outcome.result)
Ok(outcome)
}
///|
fn[T] LdapClient::record_search_result(
self : LdapClient[T],
result : LdapResult,
) -> Unit {
let step = LdapStep::new(
"search",
result.result_code.to_int(),
result.diagnostic_message,
)
self.trace.record_step(step)
self.trace.record_result(result)
}
///|
/// Send an unbind and close the connection.
pub async fn[T : LdapTransport] LdapClient::unbind(
self : LdapClient[T],
) -> Result[Unit, LdapError] {
self.session.unbind()
}
///|
/// Abandon an outstanding request.
pub async fn[T : LdapTransport] LdapClient::abandon(
self : LdapClient[T],
message_id : Int,
) -> Result[Unit, LdapError] {
self.session.abandon(message_id)
}
///|
fn[T] LdapClient::record_op(
self : LdapClient[T],
op_name : String,
result : LdapResult,
) -> Unit {
let step = LdapStep::new(
op_name,
result.result_code.to_int(),
result.diagnostic_message,
)
self.trace.record_step(step)
self.trace.record_result(result)
}
///|
/// Add an entry and record the trace.
pub async fn[T : LdapTransport] LdapClient::add(
self : LdapClient[T],
request : AddRequest,
) -> Result[AddResponse, LdapError] {
let response = match self.session.add(request) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_op("add", response.result)
Ok(response)
}
///|
/// Modify an entry and record the trace.
pub async fn[T : LdapTransport] LdapClient::modify(
self : LdapClient[T],
request : ModifyRequest,
) -> Result[ModifyResponse, LdapError] {
let response = match self.session.modify(request) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_op("modify", response.result)
Ok(response)
}
///|
/// Delete an entry and record the trace.
pub async fn[T : LdapTransport] LdapClient::delete(
self : LdapClient[T],
entry : String,
) -> Result[DelResponse, LdapError] {
let response = match self.session.delete(entry) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_op("delete", response.result)
Ok(response)
}
///|
/// Rename or move an entry and record the trace.
pub async fn[T : LdapTransport] LdapClient::modify_dn(
self : LdapClient[T],
request : ModifyDnRequest,
) -> Result[ModifyDnResponse, LdapError] {
let response = match self.session.modify_dn(request) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_op("modifyDN", response.result)
Ok(response)
}
///|
/// Compare an attribute value; compareTrue / compareFalse are recorded as
/// successful steps in the trace.
pub async fn[T : LdapTransport] LdapClient::compare(
self : LdapClient[T],
request : CompareRequest,
) -> Result[CompareResponse, LdapError] {
let response = match self.session.compare(request) {
Ok(r) => r
Err(e) => return Err(e)
}
self.record_op("compare", response.result)
Ok(response)
}
///|
/// Run a search with automatic paged-results iteration (RFC 2696): each page
/// is requested with `page_size` entries and the server-issued cookie is
/// followed until it comes back empty. All pages are merged into a single
/// outcome and one trace step per page is recorded.
pub async fn[T : LdapTransport] LdapClient::search_paged(
self : LdapClient[T],
request : SearchRequest,
page_size : Int,
) -> Result[SearchOutcome, LdapError] {
let acc = SearchOutcome::new()
let mut cookie = Bytes::new(0)
while true {
let control = paged_results_control(page_size, cookie)
let done = match
self.session.search_done(
request,
controls=[control],
on_entry=fn(entry) { acc.entries.push(entry) },
on_reference=fn(refs) { acc.references.push(refs) },
) {
Ok(d) => d
Err(e) => return Err(e)
}
self.trace.entries = acc.entries.length()
self.record_op("search", done.result)
match parse_paged_results_response(done.controls) {
Some(resp) =>
if resp.cookie.length() == 0 {
acc.result = done.result
break
} else {
cookie = resp.cookie
}
None => {
acc.result = done.result
break
}
}
}
Ok(acc)
}