### Install Novell.Directory.Ldap.NETStandard via NuGet Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Use the dotnet CLI to add the Novell.Directory.Ldap.NETStandard package to your project. ```bash dotnet add package Novell.Directory.Ldap.NETStandard ``` -------------------------------- ### Get Root DSE Information with GetRootDseInfoAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Queries the server's Root DSE to retrieve metadata like supported LDAP versions, naming contexts, and vendor information. This information is typically readable without binding. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); // Root DSE is typically readable without binding var rootDse = await cn.GetRootDseInfoAsync(); if (rootDse != null) { Console.WriteLine($"Default naming context: {rootDse.DefaultNamingContext}"); foreach (var nc in rootDse.NamingContexts) Console.WriteLine($" Naming context: {nc}"); foreach (var ext in rootDse.SupportedExtensions) Console.WriteLine($" Supported extension OID: {ext}"); } ``` -------------------------------- ### Connect and Bind to LDAP Server Source: https://github.com/dsbenghe/novell.directory.ldap.netstandard/blob/master/README.md Demonstrates how to establish a connection to an LDAP server and bind using user credentials. This is useful for verifying user passwords. ```cs await using (var cn = new LdapConnection()) { // connect await cn.ConnectAsync("<>", 389); // bind with an username and password // this how you can verify the password of an user await cn.BindAsync("<>", "<>"); // call ldap op // await cn.DeleteAsync("<>") // await cn.AddAsync(<>) } ``` -------------------------------- ### LdapConnection - Connect and Simple Bind Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Demonstrates how to establish a connection to an LDAP server and perform a simple bind authentication using a Distinguished Name and password. It also shows how to handle potential LdapExceptions during the bind process. ```APIDOC ## Connect and Simple Bind `LdapConnection` is the central class for all LDAP operations. `ConnectAsync` opens a TCP connection to the directory server, and `BindAsync` authenticates using a Distinguished Name and password. Use `await using` to automatically close and unbind on disposal. ### Method ```csharp await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", LdapConnection.DefaultPort); try { await cn.BindAsync("cn=John Doe,ou=users,dc=example,dc=com", "s3cr3t"); Console.WriteLine($"Authenticated: {cn.Bound}"); // True Console.WriteLine($"Auth DN: {cn.AuthenticationDn}"); Console.WriteLine($"Method: {cn.AuthenticationMethod}"); // "simple" } catch (LdapException ex) { Console.WriteLine($"Bind failed [{ex.ResultCode}]: {ex.Message}"); // Common codes: LdapException.InvalidCredentials (49) // LdapException.NoSuchObject (32) } ``` ``` -------------------------------- ### StartTlsAsync / StopTlsAsync - Upgrade to TLS on Existing Connection Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Explains how to use `StartTlsAsync` to upgrade an existing plain-text LDAP connection to a TLS-encrypted session and `StopTlsAsync` to revert back to a plain connection. ```APIDOC ## Upgrade to TLS on Existing Connection `StartTlsAsync` upgrades a plain-text LDAP connection (port 389) to a TLS-encrypted session using the LDAP extended operation (OID `1.3.6.1.4.1.1466.20037`). `StopTlsAsync` reverts back to an unauthenticated plain connection. ### Method ```csharp await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); // Upgrade to TLS before sending credentials await cn.StartTlsAsync(); Console.WriteLine($"TLS active: {cn.Tls}"); // True await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); // ... perform operations over encrypted channel ... // Revert to plain (reconnects anonymously) await cn.StopTlsAsync(); ``` ``` -------------------------------- ### Connect and Simple Bind with LdapConnection Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Demonstrates basic connection to an LDAP server and authentication using a Distinguished Name and password. Ensure to handle LdapException for bind failures. The connection is automatically closed and unbound upon disposal using await using. ```csharp using Novell.Directory.Ldap; // Basic connect + bind (password verification pattern) await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", LdapConnection.DefaultPort); // port 389 try { await cn.BindAsync("cn=John Doe,ou=users,dc=example,dc=com", "s3cr3t"); Console.WriteLine($"Authenticated: {cn.Bound}"); // True Console.WriteLine($"Auth DN: {cn.AuthenticationDn}"); Console.WriteLine($"Method: {cn.AuthenticationMethod}"); // "simple" } catch (LdapException ex) { Console.WriteLine($"Bind failed [{ex.ResultCode}]: {ex.Message}"); // Common codes: LdapException.InvalidCredentials (49) // LdapException.NoSuchObject (32) } ``` -------------------------------- ### AddAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Creates a new entry in the directory. Requires an LdapAttributeSet containing object class and attribute values, wrapped in an LdapEntry. ```APIDOC ## AddAsync — Add a New Directory Entry `AddAsync` creates a new entry in the directory. Build an `LdapAttributeSet` containing the required object class and attribute values, then wrap it in an `LdapEntry`. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); var attrSet = new LdapAttributeSet(); attrSet.Add(new LdapAttribute("objectClass", new[] { "top", "inetOrgPerson", "organizationalPerson" })); attrSet.Add(new LdapAttribute("cn", "Alice Brown")); attrSet.Add(new LdapAttribute("sn", "Brown")); attrSet.Add(new LdapAttribute("givenName", "Alice")); attrSet.Add(new LdapAttribute("mail", "alice@example.com")); attrSet.Add(new LdapAttribute("userPassword","initial_pass")); var newEntry = new LdapEntry("cn=Alice Brown,ou=users,dc=example,dc=com", attrSet); try { await cn.AddAsync(newEntry); Console.WriteLine("Entry added successfully."); } catch (LdapException ex) when (ex.ResultCode == LdapException.EntryAlreadyExists) { Console.WriteLine("Entry already exists."); } catch (LdapException ex) { Console.WriteLine($"Add failed [{ex.ResultCode}]: {ex.Message}"); } ``` ``` -------------------------------- ### Add New Directory Entry with AddAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Use AddAsync to create a new entry in the LDAP directory. Ensure all required object classes and attributes are correctly defined in the LdapAttributeSet before creating the LdapEntry. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); var attrSet = new LdapAttributeSet(); attrSet.Add(new LdapAttribute("objectClass", new[] { "top", "inetOrgPerson", "organizationalPerson" })); attrSet.Add(new LdapAttribute("cn", "Alice Brown")); attrSet.Add(new LdapAttribute("sn", "Brown")); attrSet.Add(new LdapAttribute("givenName", "Alice")); attrSet.Add(new LdapAttribute("mail", "alice@example.com")); attrSet.Add(new LdapAttribute("userPassword","initial_pass")); var newEntry = new LdapEntry("cn=Alice Brown,ou=users,dc=example,dc=com", attrSet); try { await cn.AddAsync(newEntry); Console.WriteLine("Entry added successfully."); } catch (LdapException ex) when (ex.ResultCode == LdapException.EntryAlreadyExists) { Console.WriteLine("Entry already exists."); } catch (LdapException ex) { Console.WriteLine($"Add failed [{ex.ResultCode}]: {ex.Message}"); } ``` -------------------------------- ### LdapConnectionOptions - SSL/TLS and Certificate Configuration Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Illustrates how to configure `LdapConnectionOptions` to enable SSL/TLS, set TLS protocols, configure certificate validation callbacks, specify client certificates, and filter IP addresses before establishing a connection. ```APIDOC ## SSL/TLS and Certificate Configuration `LdapConnectionOptions` is passed to the `LdapConnection` constructor to configure transport security, client certificates, IP filtering, and certificate validation callbacks before the connection is made. ### Method ```csharp var options = new LdapConnectionOptions() .UseSsl() .ConfigureSslProtocols(SslProtocols.Tls12 | SslProtocols.Tls13) .CheckCertificateRevocation() .ConfigureRemoteCertificateValidationCallback( (sender, cert, chain, errors) => { // Accept self-signed certs in dev; use errors == SslPolicyErrors.None in prod return errors == SslPolicyErrors.None || errors == SslPolicyErrors.RemoteCertificateChainErrors; }) .ConfigureClientCertificates(new[] { X509Certificate.CreateFromCertFile("/certs/client.crt") }) .ConfigureIpAddressFilter(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6); await using var cn = new LdapConnection(options); await cn.ConnectAsync("ldaps.example.com", LdapConnection.DefaultSslPort); // port 636 await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); ``` ``` -------------------------------- ### Upgrade Connection to TLS with StartTlsAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Initiates a TLS handshake over an existing plain-text LDAP connection (typically on port 389) using the StartTLS extended operation. This allows for secure communication without needing a dedicated LDAPS port. The connection can be reverted to plain text using StopTlsAsync. ```csharp await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); // Upgrade to TLS before sending credentials await cn.StartTlsAsync(); Console.WriteLine($"TLS active: {cn.Tls}"); // True await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); // ... perform operations over encrypted channel ... // Revert to plain (reconnects anonymously) await cn.StopTlsAsync(); ``` -------------------------------- ### Fetch Directory Schema with FetchSchemaAsync and GetSchemaDnAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Retrieves the schema subentry's DN using GetSchemaDnAsync and then loads the full LdapSchema object with FetchSchemaAsync. This object contains attribute type, object class, matching rule, and syntax definitions. Requires binding to the LDAP server. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); // Get the schema DN from the root DSE string schemaDn = await cn.GetSchemaDnAsync(); Console.WriteLine($"Schema DN: {schemaDn}"); // Load the schema LdapSchema schema = await cn.FetchSchemaAsync(schemaDn); // Enumerate attribute types foreach (var attrName in schema.AttributeNames) { var attrDef = schema.GetAttributeSchema(attrName); Console.WriteLine($"Attribute: {attrDef.Names[0]}, OID: {attrDef.Id}"); } // Enumerate object classes foreach (var ocName in schema.ObjectClassNames) { var oc = schema.GetObjectClassSchema(ocName); Console.WriteLine($"ObjectClass: {oc.Names[0]}, Type: {oc.Type}"); } ``` -------------------------------- ### Paginated LDAP Search with SimplePagedResultsControl Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Implement paginated retrieval of large result sets using SimplePagedResultsControl. Send the control with a desired page size on each search, and use the response cookie to fetch subsequent pages. ```csharp using Novell.Directory.Ldap; using Novell.Directory.Ldap.Controls; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); byte[] cookie = SimplePagedResultsControl.GetEmptyCookie; int pageSize = 100; int pageNumber = 0; do { var pageControl = new SimplePagedResultsControl(pageSize, cookie); var cons = new LdapSearchConstraints { MaxResults = 0 }; cons.SetControls(pageControl); var results = await cn.SearchAsync( "ou=users,dc=example,dc=com", LdapConnection.ScopeSub, "(objectClass=inetOrgPerson)", new[] { "cn", "mail" }, false, cons ); int count = 0; await foreach (var entry in results) { Console.WriteLine($"[Page {++pageNumber}] {entry.GetAttribute("cn")?.StringValue}"); count++; } Console.WriteLine($" Page entries: {count}"); // Extract cookie from response controls cookie = SimplePagedResultsControl.GetEmptyCookie; var responseControls = cn.ResponseControls; if (responseControls != null) { foreach (var ctrl in responseControls) { if (ctrl is SimplePagedResultsControl pagedCtrl) { cookie = pagedCtrl.Cookie; Console.WriteLine($" Server estimates total: {pagedCtrl.Size}"); break; } } } } while (cookie != null && cookie.Length > 0); Console.WriteLine("All pages retrieved."); ``` -------------------------------- ### Configure SSL/TLS and Certificate Validation Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Sets up an LdapConnection with SSL/TLS enabled, specifying TLS protocols, certificate revocation checking, a custom remote certificate validation callback, client certificates, and an IP address filter. This configuration is applied before establishing the connection. ```csharp using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Net.Security; using Novell.Directory.Ldap; // SSL connection with custom certificate validation var options = new LdapConnectionOptions() .UseSsl() .ConfigureSslProtocols(SslProtocols.Tls12 | SslProtocols.Tls13) .CheckCertificateRevocation() .ConfigureRemoteCertificateValidationCallback( (sender, cert, chain, errors) => { // Accept self-signed certs in dev; use errors == SslPolicyErrors.None in prod return errors == SslPolicyErrors.None || errors == SslPolicyErrors.RemoteCertificateChainErrors; }) .ConfigureClientCertificates(new[] { X509Certificate.CreateFromCertFile("/certs/client.crt") }) .ConfigureIpAddressFilter(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6); await using var cn = new LdapConnection(options); await cn.ConnectAsync("ldaps.example.com", LdapConnection.DefaultSslPort); // port 636 await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); ``` -------------------------------- ### BindAsync(SaslRequest) Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Performs SASL (Simple Authentication and Security Layer) binding for authentication. Supports mechanisms like EXTERNAL, DIGEST-MD5, and GSSAPI. Custom ISaslClientFactory can be registered for non-default mechanisms. ```APIDOC ## BindAsync(SaslRequest) ### Description Performs SASL binding for authentication using various mechanisms. Supports custom ISaslClientFactory registration for non-default mechanisms. ### Method `BindAsync` ### Parameters - `saslRequest` (SaslRequest) - An object representing the SASL request, such as `ExternalSaslRequest`. ### Request Example ```csharp using Novell.Directory.Ldap; using Novell.Directory.Ldap.Sasl; // SASL EXTERNAL — authenticate with a client certificate already configured in LdapConnectionOptions var options = new LdapConnectionOptions() .UseSsl() .ConfigureClientCertificates(new[] { X509Certificate.CreateFromCertFile("/certs/client.crt") }); await using var cn = new LdapConnection(options); await cn.ConnectAsync("ldaps.example.com", 636); var saslRequest = new ExternalSaslRequest(authorizationId: "dn:cn=app,dc=example,dc=com"); try { await cn.BindAsync(saslRequest); Console.WriteLine($"SASL bound, method: {cn.AuthenticationMethod}"); // "sasl" } catch (LdapException ex) { Console.WriteLine($"SASL bind failed: {ex.Message}"); } ``` ### Response - Success: The connection is bound using SASL authentication. - Error: Throws `LdapException` if the bind fails. ``` -------------------------------- ### GetRootDseInfoAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Queries the server's Root DSE to retrieve server metadata including supported LDAP versions, naming contexts, supported extensions and controls, and vendor information. ```APIDOC ## GetRootDseInfoAsync — Root DSE Information ### Description Queries the server's Root DSE to retrieve server metadata including supported LDAP versions, naming contexts, supported extensions and controls, and vendor information. ### Method `GetRootDseInfoAsync` (Extension Method) ### Parameters None ### Request Example ```csharp await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); var rootDse = await cn.GetRootDseInfoAsync(); if (rootDse != null) { Console.WriteLine($"Default naming context: {rootDse.DefaultNamingContext}"); foreach (var nc in rootDse.NamingContexts) Console.WriteLine($" Naming context: {nc}"); foreach (var ext in rootDse.SupportedExtensions) Console.WriteLine($" Supported extension OID: {ext}"); } ``` ### Response #### Success Response - **rootDse** (`LdapRootDse` or null): An object containing server metadata or null if not found. - **DefaultNamingContext** (string): The default naming context of the server. - **NamingContexts** (string[]): An array of supported naming contexts. - **SupportedExtensions** (string[]): An array of supported extension OIDs. ### Response Example ```json { "DefaultNamingContext": "dc=example,dc=com", "NamingContexts": ["dc=example,dc=com"], "SupportedExtensions": ["1.3.6.1.4.1.4203.1.5.1"] } ``` ``` -------------------------------- ### Configure Search Constraints with LdapSearchConstraints Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Configures per-operation limits for LDAP searches, including maximum results, time limits, alias dereferencing, batch size, and referral following. Requires binding to the LDAP server. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); var constraints = new LdapSearchConstraints { MaxResults = 500, // Limit to 500 entries ServerTimeLimit = 30, // Server aborts after 30 seconds TimeLimit = 35000, // Client abandons after 35 seconds (ms) Dereference = LdapSearchConstraints.DerefAlways, BatchSize = 0, // Block until all results received ReferralFollowing = true, // Automatically follow referrals HopLimit = 5, }; var results = await cn.SearchAsync( "dc=example,dc=com", LdapConnection.ScopeSub, "(objectClass=groupOfNames)", new[] { "cn", "member", "description" }, false, constraints ); await foreach (var entry in results) Console.WriteLine(entry.Dn); ``` -------------------------------- ### SearchAsyncAsList for Collecting All Search Results Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt A convenience method that executes an LDAP search and collects all results into a List. Useful for scenarios where all entries are needed at once. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); List entries = await cn.SearchAsyncAsList( @base: "ou=groups,dc=example,dc=com", scope: LdapConnection.ScopeOne, filter: "(objectClass=groupOfNames)", attrs: new[] { "cn", "member" }, typesOnly: false ); foreach (var entry in entries) { var members = entry.GetAttribute("member")?.StringValueArray ?? Array.Empty(); Console.WriteLine($"Group: {entry.GetAttribute("cn")?.StringValue}, Members: {members.Length}"); } ``` -------------------------------- ### SearchAsyncAsList Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt A convenience extension method that performs an LDAP search and collects all results into a `List`. This simplifies handling search results by avoiding the need for manual asynchronous iteration. ```APIDOC ## SearchAsyncAsList ### Description This extension method performs an LDAP search and collects all results into a `List`, simplifying result handling. ### Method `SearchAsyncAsList` ### Parameters - `@base` (string) - The Distinguished Name (DN) of the entry to start the search from. - `scope` (int) - The scope of the search (e.g., `LdapConnection.ScopeOne` for one level search). - `filter` (string) - The LDAP search filter string. - `attrs` (string[]) - An array of attribute names to retrieve for each entry. - `typesOnly` (bool) - If true, only attribute syntaxes are returned, not values. ### Request Example ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); List entries = await cn.SearchAsyncAsList( @base: "ou=groups,dc=example,dc=com", scope: LdapConnection.ScopeOne, filter: "(objectClass=groupOfNames)", attrs: new[] { "cn", "member" }, typesOnly: false ); foreach (var entry in entries) { var members = entry.GetAttribute("member")?.StringValueArray ?? Array.Empty(); Console.WriteLine($"Group: {entry.GetAttribute("cn")?.StringValue}, Members: {members.Length}"); } ``` ### Response - Success: Returns a `List` containing all search results. - Error: Throws `LdapException` if the search fails. ``` -------------------------------- ### SimplePagedResultsControl Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt `SimplePagedResultsControl` (RFC 2696) enables paginated retrieval of large result sets. Send the control with the desired page size on each search; the response control provides a cookie to continue to the next page. ```APIDOC ## SimplePagedResultsControl — Paginated Search `SimplePagedResultsControl` (RFC 2696) enables paginated retrieval of large result sets. Send the control with the desired page size on each search; the response control provides a cookie to continue to the next page. ```csharp using Novell.Directory.Ldap; using Novell.Directory.Ldap.Controls; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); byte[] cookie = SimplePagedResultsControl.GetEmptyCookie; int pageSize = 100; int pageNumber = 0; do { var pageControl = new SimplePagedResultsControl(pageSize, cookie); var cons = new LdapSearchConstraints { MaxResults = 0 }; cons.SetControls(pageControl); var results = await cn.SearchAsync( "ou=users,dc=example,dc=com", LdapConnection.ScopeSub, "(objectClass=inetOrgPerson)", new[] { "cn", "mail" }, false, cons ); int count = 0; await foreach (var entry in results) { Console.WriteLine($"[Page {++pageNumber}] {entry.GetAttribute("cn")?.StringValue}"); count++; } Console.WriteLine($" Page entries: {count}"); // Extract cookie from response controls cookie = SimplePagedResultsControl.GetEmptyCookie; var responseControls = cn.ResponseControls; if (responseControls != null) { foreach (var ctrl in responseControls) { if (ctrl is SimplePagedResultsControl pagedCtrl) { cookie = pagedCtrl.Cookie; Console.WriteLine($" Server estimates total: {pagedCtrl.Size}"); break; } } } } while (cookie != null && cookie.Length > 0); Console.WriteLine("All pages retrieved."); ``` ``` -------------------------------- ### SearchAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Performs an LDAP search operation within the directory. Returns an `IAsyncEnumerable` for asynchronous iteration over the results. Search depth is controlled by scope constants. ```APIDOC ## SearchAsync ### Description Performs an LDAP search and returns results as an `IAsyncEnumerable`. The search depth can be controlled using scope constants (`ScopeBase`, `ScopeOne`, `ScopeSub`). ### Method `SearchAsync` ### Parameters - `@base` (string) - The Distinguished Name (DN) of the entry to start the search from. - `scope` (int) - The scope of the search (e.g., `LdapConnection.ScopeSub` for subtree search). - `filter` (string) - The LDAP search filter string. - `attrs` (string[]) - An array of attribute names to retrieve for each entry. - `typesOnly` (bool) - If true, only attribute syntaxes are returned, not values. ### Request Example ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); // Search all users in subtree with specific attributes var results = await cn.SearchAsync( @base: "ou=users,dc=example,dc=com", scope: LdapConnection.ScopeSub, filter: "(&(objectClass=inetOrgPerson)(mail=*@example.com))", attrs: new[] { "cn", "mail", "telephoneNumber" }, typesOnly: false ); await foreach (var entry in results) { var cn_val = entry.GetAttribute("cn")?.StringValue ?? "(none)"; var mail = entry.GetAttribute("mail")?.StringValue ?? "(none)"; Console.WriteLine($"DN: {entry.Dn} CN: {cn_val} Mail: {mail}"); } ``` ### Response - Success: Returns an `IAsyncEnumerable` containing the search results. - Error: Throws `LdapException` if the search fails. ``` -------------------------------- ### ReadAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Retrieves a single directory entry by its Distinguished Name (DN). Optionally, an array of attribute names can be provided to limit the returned data. Throws `LdapException` if the entry is not found. ```APIDOC ## ReadAsync ### Description Retrieves a single directory entry by its Distinguished Name (DN). Allows specifying attributes to retrieve and throws `LdapException` if the entry does not exist. ### Method `ReadAsync` ### Parameters - `dn` (string) - The Distinguished Name (DN) of the entry to read. - `attributes` (string[]) - Optional. An array of attribute names to retrieve. If null or empty, all attributes are returned. ### Request Example ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); try { // Read specific attributes only var entry = await cn.ReadAsync( "cn=Jane Smith,ou=users,dc=example,dc=com", new[] { "cn", "mail", "jpegPhoto", "memberOf" } ); Console.WriteLine($"DN: {entry.Dn}"); Console.WriteLine($"Mail: {entry.GetAttribute("mail")?.StringValue}"); var groups = entry.GetAttribute("memberOf")?.StringValueArray ?? Array.Empty(); foreach (var g in groups) Console.WriteLine($ ``` -------------------------------- ### ReadAsync for Fetching a Single Entry by DN Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Retrieves a single directory entry using its Distinguished Name (DN). Optionally specify attributes to fetch. Throws LdapException if the entry is not found. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); try { // Read specific attributes only var entry = await cn.ReadAsync( "cn=Jane Smith,ou=users,dc=example,dc=com", new[] { "cn", "mail", "jpegPhoto", "memberOf" } ); Console.WriteLine($"DN: {entry.Dn}"); Console.WriteLine($"Mail: {entry.GetAttribute("mail")?.StringValue}"); var groups = entry.GetAttribute("memberOf")?.StringValueArray ?? Array.Empty(); foreach (var g in groups) Console.WriteLine($" Member of: {g}"); // Access binary attribute (photo) byte[] photo = entry.GetAttribute("jpegPhoto")?.ByteValue; Console.WriteLine($"Photo bytes: {photo?.Length ?? 0}"); } catch (LdapException ex) when (ex.ResultCode == LdapException.NoSuchObject) { Console.WriteLine("Entry not found."); } ``` -------------------------------- ### FetchSchemaAsync / GetSchemaDnAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Retrieves the DN of the schema subentry and loads the full LdapSchema object containing attribute type definitions, object class definitions, matching rules, and syntax definitions. ```APIDOC ## FetchSchemaAsync / GetSchemaDnAsync — Directory Schema ### Description `GetSchemaDnAsync` retrieves the DN of the schema subentry, and `FetchSchemaAsync` loads the full `LdapSchema` object containing attribute type definitions, object class definitions, matching rules, and syntax definitions. ### Method - `GetSchemaDnAsync` (Extension Method) - `FetchSchemaAsync` (Extension Method) ### Parameters - **`FetchSchemaAsync`**: `schemaDn` (string) - The distinguished name of the schema subentry. ### Request Example ```csharp await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string schemaDn = await cn.GetSchemaDnAsync(); Console.WriteLine($"Schema DN: {schemaDn}"); LdapSchema schema = await cn.FetchSchemaAsync(schemaDn); foreach (var attrName in schema.AttributeNames) { var attrDef = schema.GetAttributeSchema(attrName); Console.WriteLine($"Attribute: {attrDef.Names[0]}, OID: {attrDef.Id}"); } foreach (var ocName in schema.ObjectClassNames) { var oc = schema.GetObjectClassSchema(ocName); Console.WriteLine($"ObjectClass: {oc.Names[0]}, Type: {oc.Type}"); } ``` ### Response #### Success Response - **`GetSchemaDnAsync`**: Returns the schema DN as a string. - **`FetchSchemaAsync`**: Returns an `LdapSchema` object containing schema definitions. - **AttributeNames** (IEnumerable): Names of attribute types. - **ObjectClassNames** (IEnumerable): Names of object classes. - **GetAttributeSchema(string name)**: Retrieves attribute schema definition. - **GetObjectClassSchema(string name)**: Retrieves object class schema definition. ### Response Example ```json { "SchemaDn": "cn=schema", "AttributeNames": ["cn", "uid", "mail"], "ObjectClassNames": ["inetOrgPerson", "groupOfNames"] } ``` ``` -------------------------------- ### SearchAsync for Directory Entries Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Performs an LDAP search within a specified base, scope, and filter, returning specific attributes. The search depth is controlled by scope constants (ScopeBase, ScopeOne, ScopeSub). ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); // Search all users in subtree with specific attributes var results = await cn.SearchAsync( @base: "ou=users,dc=example,dc=com", scope: LdapConnection.ScopeSub, filter: "(&(objectClass=inetOrgPerson)(mail=*@example.com))", attrs: new[] { "cn", "mail", "telephoneNumber" }, typesOnly: false ); await foreach (var entry in results) { var cn_val = entry.GetAttribute("cn")?.StringValue ?? "(none)"; var mail = entry.GetAttribute("mail")?.StringValue ?? "(none)"; Console.WriteLine($"DN: {entry.Dn} CN: {cn_val} Mail: {mail}"); } // Output: // DN: cn=Jane Smith,ou=users,dc=example,dc=com CN: Jane Smith Mail: jane@example.com // DN: cn=Bob Jones,ou=users,dc=example,dc=com CN: Bob Jones Mail: bob@example.com ``` -------------------------------- ### LdapSearchConstraints Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Configures per-operation limits including maximum results, server and client time limits, alias dereferencing behavior, batch size, referral following, and server-side controls. ```APIDOC ## LdapSearchConstraints — Fine-Tuned Search Control ### Description `LdapSearchConstraints` configures per-operation limits including maximum results, server and client time limits, alias dereferencing behavior, batch size, referral following, and server-side controls. ### Method Used in conjunction with `LdapConnection.SearchAsync`. ### Parameters - **`MaxResults`** (int) - Optional - Maximum number of entries to return. - **`ServerTimeLimit`** (int) - Optional - Server aborts search after this many seconds. - **`TimeLimit`** (int) - Optional - Client abandons search after this many milliseconds. - **`Dereference`** (LdapSearchConstraints.DereferenceEnum) - Optional - Alias dereferencing behavior (e.g., `LdapSearchConstraints.DerefAlways`). - **`BatchSize`** (int) - Optional - Number of results to return in a batch (0 means block until all results received). - **`ReferralFollowing`** (bool) - Optional - Whether to automatically follow referrals. - **`HopLimit`** (int) - Optional - Maximum number of referrals to follow. ### Request Example ```csharp var constraints = new LdapSearchConstraints { MaxResults = 500, ServerTimeLimit = 30, TimeLimit = 35000, Dereference = LdapSearchConstraints.DerefAlways, BatchSize = 0, ReferralFollowing = true, HopLimit = 5, }; var results = await cn.SearchAsync( "dc=example,dc=com", LdapConnection.ScopeSub, "(objectClass=groupOfNames)", new[] { "cn", "member", "description" }, false, constraints ); ``` ### Response #### Success Response - **`results`** (`IAsyncEnumerable`): An asynchronous enumerable collection of `LdapEntry` objects matching the search criteria. ### Response Example ```json [ { "Dn": "cn=user1,dc=example,dc=com", "Attributes": { "cn": ["user1"], "member": ["uid=user1,ou=people,dc=example,dc=com"], "description": ["User Account 1"] } } ] ``` ``` -------------------------------- ### Compare Attribute Value with LDAP Entry Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Use CompareAsync to check if an entry contains a specific attribute and value without fetching the entire entry. This is efficient for verifying data like group membership or password validity. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string dn = "cn=Jane Smith,ou=users,dc=example,dc=com"; bool isMember = await cn.CompareAsync( dn, new LdapAttribute("memberOf", "cn=admins,ou=groups,dc=example,dc=com") ); Console.WriteLine($"Is admin group member: {isMember}"); // Verify password via compare (userPassword attribute) bool validPassword = await cn.CompareAsync( dn, new LdapAttribute("userPassword", "user_pass") ); Console.WriteLine($"Password valid: {validPassword}"); ``` -------------------------------- ### Delete Directory Entry with DeleteAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Use DeleteAsync to remove a leaf entry from the directory using its Distinguished Name (DN). This method will not remove entries with subordinate children; recursive deletion must be handled at the application level. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string dn = "cn=Alice Brown,ou=users,dc=example,dc=com"; try { await cn.DeleteAsync(dn); Console.WriteLine("Entry deleted."); } catch (LdapException ex) when (ex.ResultCode == LdapException.NoSuchObject) { Console.WriteLine("Entry does not exist."); } catch (LdapException ex) when (ex.ResultCode == LdapException.NotAllowedOnNonleaf) { Console.WriteLine("Cannot delete: entry has children."); } ``` -------------------------------- ### Handle Server-Initiated Messages with AddUnsolicitedNotificationListener Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Registers an ILdapUnsolicitedNotificationListener to receive server-initiated messages, such as the server shutdown notification. This allows the client to react to events without an explicit request. ```csharp using Novell.Directory.Ldap; public class ShutdownListener : ILdapUnsolicitedNotificationListener { public void MessageReceived(LdapExtendedResponse msg) { if (msg.Id == LdapConnection.ServerShutdownOid) Console.WriteLine("[ALERT] Server is shutting down!"); else Console.WriteLine($"Unsolicited OID: {msg.Id}"); } } await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); var listener = new ShutdownListener(); cn.AddUnsolicitedNotificationListener(listener); // ... keep connection alive and handle notifications ... cn.RemoveUnsolicitedNotificationListener(listener); ``` -------------------------------- ### Modify Existing Entry with ModifyAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt ModifyAsync applies multiple LDAP modifications (ADD, DELETE, REPLACE) atomically to an existing entry. Ensure the Distinguished Name (DN) is correct and the modifications are valid for the entry's schema. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string dn = "cn=Alice Brown,ou=users,dc=example,dc=com"; var mods = new LdapModification[] { // Replace existing email new LdapModification(LdapModification.Replace, new LdapAttribute("mail", "alice.brown@example.com")), // Add a new phone number new LdapModification(LdapModification.Add, new LdapAttribute("telephoneNumber", "+1-555-0100")), // Remove the description attribute entirely new LdapModification(LdapModification.Delete, new LdapAttribute("description")), }; try { await cn.ModifyAsync(dn, mods); Console.WriteLine("Entry modified."); } catch (LdapException ex) { Console.WriteLine($"Modify failed [{ex.ResultCode}]: {ex.Message}"); } ``` -------------------------------- ### Rename or Move Entry with RenameAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt RenameAsync changes an entry's Relative Distinguished Name (RDN) and can move it to a new parent DN. The deleteOldRdn parameter determines if the old RDN value is removed from the entry's attributes. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); // Simple rename within same container await cn.RenameAsync( dn: "cn=Alice Brown,ou=users,dc=example,dc=com", newRdn: "cn=Alice Smith", deleteOldRdn: true ); // Move to a different OU await cn.RenameAsync( dn: "cn=Alice Smith,ou=users,dc=example,dc=com", newRdn: "cn=Alice Smith", newParentdn: "ou=managers,dc=example,dc=com", deleteOldRdn: false ); Console.WriteLine("Entry renamed/moved."); ``` -------------------------------- ### CompareAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt `CompareAsync` checks whether a specific entry contains a given attribute/value pair without fetching the entry. Returns `true` if the entry has the value, `false` otherwise. ```APIDOC ## CompareAsync — Attribute Value Assertion `CompareAsync` checks whether a specific entry contains a given attribute/value pair without fetching the entry. Returns `true` if the entry has the value, `false` otherwise. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string dn = "cn=Jane Smith,ou=users,dc=example,dc=com"; bool isMember = await cn.CompareAsync( dn, new LdapAttribute("memberOf", "cn=admins,ou=groups,dc=example,dc=com") ); Console.WriteLine($"Is admin group member: {isMember}"); // Verify password via compare (userPassword attribute) bool validPassword = await cn.CompareAsync( dn, new LdapAttribute("userPassword", "user_pass") ); Console.WriteLine($"Password valid: {validPassword}"); ``` ``` -------------------------------- ### BindAsync with SaslRequest for SASL EXTERNAL Authentication Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Authenticates using SASL EXTERNAL mechanism with a client certificate. Ensure the certificate is configured in LdapConnectionOptions and the server supports this mechanism. ```csharp using Novell.Directory.Ldap; using Novell.Directory.Ldap.Sasl; // SASL EXTERNAL — authenticate with a client certificate already configured in LdapConnectionOptions var options = new LdapConnectionOptions() .UseSsl() .ConfigureClientCertificates(new[] { X509Certificate.CreateFromCertFile("/certs/client.crt") }); await using var cn = new LdapConnection(options); await cn.ConnectAsync("ldaps.example.com", 636); var saslRequest = new ExternalSaslRequest(authorizationId: "dn:cn=app,dc=example,dc=com"); try { await cn.BindAsync(saslRequest); Console.WriteLine($"SASL bound, method: {cn.AuthenticationMethod}"); // "sasl" } catch (LdapException ex) { Console.WriteLine($"SASL bind failed: {ex.Message}"); } ``` -------------------------------- ### DeleteAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Removes a leaf entry from the directory by its Distinguished Name. It will not remove entries that have subordinate children. ```APIDOC ## DeleteAsync — Delete a Directory Entry `DeleteAsync` removes a leaf entry from the directory by its Distinguished Name. It will not remove entries that have subordinate children (use recursive deletes at the application level). ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string dn = "cn=Alice Brown,ou=users,dc=example,dc=com"; try { await cn.DeleteAsync(dn); Console.WriteLine("Entry deleted."); } catch (LdapException ex) when (ex.ResultCode == LdapException.NoSuchObject) { Console.WriteLine("Entry does not exist."); } catch (LdapException ex) when (ex.ResultCode == LdapException.NotAllowedOnNonleaf) { Console.WriteLine("Cannot delete: entry has children."); } ``` ``` -------------------------------- ### ModifyAsync Source: https://context7.com/dsbenghe/novell.directory.ldap.netstandard/llms.txt Applies one or more LdapModification operations (ADD, DELETE, or REPLACE) to an existing entry atomically. All modifications in an array are applied as a single operation. ```APIDOC ## ModifyAsync — Modify an Existing Entry `ModifyAsync` applies one or more `LdapModification` operations (ADD, DELETE, or REPLACE) to an existing entry atomically. All modifications in an array are applied as a single operation. ```csharp using Novell.Directory.Ldap; await using var cn = new LdapConnection(); await cn.ConnectAsync("ldap.example.com", 389); await cn.BindAsync("cn=admin,dc=example,dc=com", "admin_pass"); string dn = "cn=Alice Brown,ou=users,dc=example,dc=com"; var mods = new LdapModification[] { // Replace existing email new LdapModification(LdapModification.Replace, new LdapAttribute("mail", "alice.brown@example.com")), // Add a new phone number new LdapModification(LdapModification.Add, new LdapAttribute("telephoneNumber", "+1-555-0100")), // Remove the description attribute entirely new LdapModification(LdapModification.Delete, new LdapAttribute("description")), }; try { await cn.ModifyAsync(dn, mods); Console.WriteLine("Entry modified."); } catch (LdapException ex) { Console.WriteLine($"Modify failed [{ex.ResultCode}]: {ex.Message}"); } ``` ```