Oct 6 2009

Using .NET C# LDAP Library

Mohammed Al-Atari

URL: http://www.novell.com/coolsolutions/feature/11204.html

.NET C# LDAP library provides easy access to any LDAP compliant directory from managed code. The library enables the developers to write LDAP enabled applications that access, manage, and update information stored in Novell eDirectory or other LDAP-aware directories. We assume that the user of the library is familiar with general understanding of LDAP before using the class provided in the library. This document provides an overview of the .NET C# LDAP library and programming code examples.

 

Introduction

 

The .NET C# LDAP Library provides easy access to any LDAP compliant directory from managed code. The .NET C# Library enables you to write applications that access, manage, and update information stored in Novell eDirectory or other LDAP compliant directories. The library is packaged into the Novell.Directory.Ldap namespace and contains a number of samples demonstrating common directory operations. These samples are packaged along with the .NET C# library source code.

 

Overview of the LDAP model

 

Lightweight Directory Access Protocol (LDAP) is described in RFC 2251-2256 and RFC 2829-2830. It defines a lightweight access mechanism in which clients send requests to and receive responses from LDAP servers.

The LDAP information model comes from X.500 and is based on the entry, which contains information about some object (for example, a person). Entries are composed of attributes, which have a type and one or more values. Each attribute has a syntax that determines what kinds of values are allowed in the attribute (for example, ASCII characters, a jpeg photograph, etc.) and how those values behave during directory operations (for example, is case significant during comparisons).

Entries may be organized in a tree structure, usually based on political, geographical, and organizational boundaries. Other structures are possible, including a flat namespace. Each entry is uniquely named relative to its sibling entries by its relative distinguished name (RDN) consisting of one or more distinguished attribute values from the entry. At the most, one value from each attribute may be used in the RDN. For example, the entry for the person “James Smith” might be named with the “Jonathan Smith” value from the CN (commonName) attribute.

A globally unique name for an entry, called a distinguished name or DN, is constructed by concatenating the sequence of RDNs from the entry up to the root of the tree. For example, if James worked for the Novell Inc, the DN of his Novell entry might be “cn= Jonathan smith,o=Novell,c=US”. The DN format used by LDAP is defined in RFC2253.

Operations are provided to authenticate, search and retrieve information, modify, add and delete entries from the tree.

An LDAP server may return referrals if it cannot completely service a request (for example if the request specifies a directory base outside of the tree managed by the server). The .NET C# LDAP library offers a programmer the following three options:

  • * catch the referrals as exceptions and explicitly issue new requests to the referred-to servers
  • * provide an object to establish a new connection to a referred-to server
  • * let the library automatically follow the referrals

 

Overview of .NET C# LDAP Library

 

C# LDAP library is released under the Novell.Directory.Ldap namespace and all the class files under this namespace are further divided into six different namespaces:-

image005

Novell.Directory.Ldap

This namespace allows you to manage entries and schema definitions on LDAPv3 compliant servers. It provides classes for the core C# LDAP library, which is most frequently used by applications. These classes are based on JavaLDAP Internet drafts maintained by IETF.

NOTE: The schema functionality is yet to be implemented in the .NET C# Library.

Novell.Directory.Ldap.Asn1

The classes provided in this namespace can be used to encode and decode Abstract Syntax Notation One (ASN.1) object types using Basic Encoding Rules (BER). ASN.1 is the language used by the OSI protocols for describing abstract syntax. ASN.1 as defined in ISO documents 8824.2 and 8825.2. BER are historically the original encoding rules for ASN.1.

The LDAP protocol uses the BER encoding format and this package includes classes that allow ASN.1 to be encoded and decoded into the BER format. However, the classes have been built to be flexible enough to allow an application to provide its own ASN1 encoder class. This class could encode data into any encoding format. For example, a particular application might want to use Packed Encoding Rules (PER) to encode the supported ASN.1 objects. This application would however be required to supply its own PER encoder and PER decoder classes. These application-provided classes will need to implement the ASN1Encoder and ASN1Decoder interfaces defined in this package.

Note that LDAP uses BER encoding and the Novell provided namespace already includes a BEREncoder and BERDEcoder class. These classes can be used by third-party developers who wish to develop new LDAP controls or extensions. These classes could also be used by an arbitrary .NET C# application that wishes to encode and decode data as defined in the ASN.1 format.

Novell.Directory.Ldap.Controls

Provides classes for using LDAP controls. This namespace uses LDAP controls that are supported in LDAPv3. The use of these controls requires an LDAP server that supports them.

Novell.Directory.Ldap.Extensions

Provides classes for using the Novell LDAP extensions that manage replicas, naming contexts, and the synchronization of replicas and the schema. This namespace uses LDAP extensions that are supported in LDAPv3. These extensions require the LDAP server to run on eDirectory.

Novell.Directory.Ldap.Rfc2251

Provides classes that represent protocol elements as defined by the IETF LDAP RFC 2251. This namespace is designed to work on LDAPv3 servers. It does not support the T.61 character set used by the LDAPv2 protocol.

Novell.Directory.Ldap.Utilclass

Provides utility classes for use by LDAP applications. This namespace includes the DN class and RDN class, supporting DN and RDN encapsulation respectively. It also provides classes to perform client functions related to the LDAP protocol. This package is designed to work on LDAPv3 servers. It does not support the T.61 character set used by LDAPv2.

The central LDAP class is LdapConnection. It provides methods to establish an authenticated or anonymous connection to an LDAP server, as well as methods to search for, modify, compare, and delete entries in the directory.

The LdapConnection class also provides fields for storing settings that are specific to the LDAP session (such as limits on the number of results returned or timeout limits). An LdapConnection object can be cloned, allowing objects to share a single network connection but use different settings (using LdapConstraints or LdapSearchConstraints).

A synchronous search conducted by an LdapConnection object returns results in an LdapSearchResults object, which can be enumerated to access the entries found. Each entry (represented by an LdapEntry object) provides access to the attributes (represented by LdapAttribute objects) returned for that entry. Each attribute can produce the values found as byte arrays or as Strings.

The LDAP Asynchronous Methods

The LDAP protocol provides synchronous as well as asynchronous directory access methods. All asynchronous methods return MessageQueue objects (either LdapResponseQueue or LdapSearchQueue ) and also take a MessageQueue object as an input. MessageQueue is associated with the request, and it is the responsibility of the client to read the messages out of the queue and process them.

Messages retrieved from an LdapResponseQueue are result objects derived from LdapResponse. Messages retrieved from an LdapSearchQueue are either result objects derived from LdapResponse, search results, or search result references.

An asynchronous search conducted by an LdapConnection object returns results via the getResponse method of the LdapSearchQueue returned by the search operation. The getResponse method typically returns an LdapSearchResult object which has an Entry Property that returns the LdapEntry that represents the search entry.

None of the ancillary asynchronous classes are intended to be instantiated by a client, so they lack public constructors.

 

Overview of C# LDAP API Use

 

An application generally uses the C# LDAP API in four steps.

  • * Construct an LdapConnection. Initialize an LDAP session with a directory server. The LdapConnection.Connect () call establishes a handle to the session, allowing multiple sessions to be open at the same time, on different instances of LdapConnection.

  • * Authenticate to the LDAP server with LdapConnection.Bind(). We support only cleartext authentication. SSL/TLS support is yet to be added.

  • * Perform LDAP operations and obtain results. The synchronous version of LdapConnection.Search() returns an LdapSearchResults which can be enumerated to access all entries found. The asynchronous version of LdapConnection.Search() returns an LdapSearchQueue, which is used to read the results of the search. LdapConnection.read() returns a single entry.

  • * Close the connection. The LdapConnection.Disconnect() call closes the connection.

There are both synchronous and asynchronous versions of the LDAP protocol operations in this Library. Synchronous methods do not return until the operation has completed.

Asynchronous methods take a MessageQueue parameter (either LdapResponseQueue or LDAPSearchQueue) and return a MessageQueue object which is used to enumerate the responses from the server. A loop is typically used to read from the MessageQueue object, which blocks until there is a response available, until the operation has completed.

An LdapResponseQueue may be shared between operations, for multiplexing the results. In this case, the object returned on one operation is passed into one or more other operations, rather than passing in null.

For the asynchronous methods, exceptions are raised only for connection errors. LDAP result messages are converted into LdapResponse objects, which are to be checked by the client for errors and referrals, whereas, the synchronous methods throw an LdapException on result codes other than 0.

To facilitate user feedback during synchronous searches, intermediate search results can be obtained before the entire search operation is completed by specifying the number of entries to return at a time.

Errors result in the throwing of an LdapException, with a specific error code and context-specific textual information available.

If null is passed as the value of an LdapConstraints or LdapSearchConstraints parameter to an operation, the default constraints are used for that operation.

If null is passed as the value of a DN to an operation it is treated as if it was the empty string.

The API doesn’t distinguish between Ldap search continuation references and Ldap referrals, presenting a unified interface to the client for handling the two.

Implementations of the API MUST ensure that the LdapConnection class is thread-safe. Other classes and methods MAY be thread-safe and the implementor MUST indicate which classes and methods are thread-safe.

Getting Started

This section contains information about how to setup your development environment for using Novell.Directory.Ldap namespace and about basic LDAP operations performed using the managed code.

The following is a list of some basic LDAP operations:

  • 1. Binding an entry to a LDAP server.
  • 2. Searching the directory.
  • 3. Creating an entry in the directory.
  • 4. Modifying an entry property.
  • 5. Renaming an entry.
  • 6. Moving an entry.
  • 7. Deleting an entry.

Setting Up Your Development Environment

To use Novell.Directroy.Ldap on the Windows OS, you need to setup your Microsoft .NET project using Novell.Directory.Ldap. To use Novell.Directory.Ldap:

  • 1. Copy Novell.Directory.Ldap.dll to an appropriate location in your project.
  • 2. Start Visual Studio .NET.
  • 3. Select File->New->Project.
  • 4. In the Project Type column, select the project type to create C#.
  • 5. In the Template column, select a project template (like Console Application, Windows Application).
  • 6. Name your project.
  • 7. Click OK to create your new project.
  • 8. Select Project->Add reference->Browse.
  • 9. In Browse select Novell.Directory.Ldap.dll from the location you copied in step.

image006

  • 10. Click OK to Add Reference.
  • 11. Add the following line to your code:
    Using Novell.Directory.Ldap;

Note: Adding the namespace using statement to your code is unnecessary, but can simplify object names. If you do not add this statement, then you must declare an object as Novell.Directory.Ldap.LdapConnection, instead of LdapConnection.

Binding an entry to a LDAP server

This section contains information about how to bind an entry to an LDAP server using the Novell.Directory.Ldap namespace. The bind operation allows the entry to authenticate to the server. An entry in a directory is uniquely identified using its distinguished name, that is, DN. A client application can choose whether it wants to bind to the directory using an identity (for example, enter the DN and password as credentials) or wants to bind anonymously. The C# code fragments below shows how to bind a user entry to an LDAP server:

Anonymous Binding

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect (ldapHost,ldapPort);

//Bind function with null user dn and password value will perform anonymous bind

//to LDAP server

ldapConn.Bind (null, null);

Binding using an Identity

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

Searching the Directory

The most common directory task is searching. To perform a search, your application must first bind to the LDAP server and then select the root point in the directory (base object DN). For optimal performance, select a point that will provide the smallest result set.

The following diagram illustrates a search that selects the Marketing container as the root point for search. Decisions that must be made when setting up this search include deciding which type of object to search for, and setting up a search filter for that type of object.

image007

Searching is performed using the LdapConnection.Search function. When you perform an LDAPsearch, you need to specify the following five basic parameters:

Search Base

The DN of the entry where you would like the search to begin. An empty string equals root.

Search Scope

How deep you would like the search to extend down the tree.

Search Filter

Defines which entries are to be returned.

Attribute List

A null-terminated array of strings indicating which attributes to return for each matching entry.

Types Only

A boolean specifying whether you would like to return only the attribute names or the attribute types and the attribute values.

Search Base

The search base parameter specifies the DN of the entry where you would like to begin the search, such as ou=development, o=acme.

If you would like the search to begin at the tree root pass an empty string.

NOTE: Beginning a search at the tree root is handled differently by the various LDAP server implementations. If your search does not return results, read the root DSE to retrieve valid naming contexts for a vaild starting point.

Search Scope

The search scope parameter specifies the depth of the search and can be one of three values:

  • * SCOPE_BASE: Only the entry specified as the search base is included in the search. This is used when you already know the DN of the object and you would like to read its attributes. The read method may also be used to read the values of a single entry.
  • * SCOPE_ONE: Objects one level below the base (but not including the base) are included in the search. If we specified o=acme as our search base, then entries in the o=acme container would be included, but not the object o=acme.
  • * SCOPE_SUB: All objects below the base, including the base itself, are included in the search.

Search Filter

The search filter defines the entries that will be returned by the search. The LDAP search filter grammar is specified in RFC 2254 and 2251. The grammar uses ABNF notation. If you are looking for all employees with a title of engineer, the search filter would be (title=engineer).

Getting Search Results

Each result returned by Search function is stored in a MessageQueue which can be retrieved either in a synchronous way using LdapSearchResults class or in an asynchronous way using LdapSearchQueue class.

The C# code fragments below shows how to do a synchronous and asynchronous search in a LDAP server:

Searching the Directory – Asynchronous

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

// Searches in the Marketing container and return all child entries just below this

//container i.e Single level search

LdapSearchQueue queue=ldapConn.Search ( searchBase,

LdapConnection.SCOPE_ONE,

searchFilter,

null,

false,

(LdapSearchQueue) null,

(LdapSearchConstraints) null );

LdapMessage message;

while ((message = queue.getResponse()) != null)

{

if (message is LdapSearchResult)

{

LdapEntry entry = ((LdapSearchResult) message).Entry;

System.Console.Out.WriteLine(“\n” + entry.DN);

System.Console.Out.WriteLine(“\tAttributes: “);

LdapAttributeSet attributeSet = entry.getAttributeSet();

System.Collections.IEnumerator ienum = attributeSet.GetEnumerator();

while(ienum.MoveNext())

{

LdapAttribute attribute=(LdapAttribute)ienum.Current;

string attributeName = attribute.Name;

string attributeVal = attribute.StringValue;

Console.WriteLine( attributeName + “value:” +

attributeVal);

}

}

}

ldapConn.Disconnect();

Searching the Directory – Synchronous

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

// Searches in the Marketing container and return all child entries just below this

//container i.e. Single level search

LdapSearchResults lsc=ldapConn.Search(“ou=Marketing,o=Sales”,

LdapConnection.SCOPE_ONE,

“objectClass=*”,

null,

false);

while (lsc.hasMore())

{

LdapEntry nextEntry = null;

try

{

nextEntry = lsc.next();

}

catch(LdapException e)

{

Console.WriteLine(“Error: ” + e.LdapErrorMessage);

// Exception is thrown, go for next entry

continue;

}

Console.WriteLine(“\n” + nextEntry.DN);

LdapAttributeSet attributeSet = nextEntry.getAttributeSet();

System.Collections.IEnumerator ienum = attributeSet.GetEnumerator();

while(ienum.MoveNext())

{

LdapAttribute attribute=(LdapAttribute)ienum.Current;

string attributeName = attribute.Name;

string attributeVal = attribute.StringValue;

Console.WriteLine( attributeName + “value:” + attributeVal);

}

}

ldapConn.Disconnect();

Creating an Entry in the Directory

Adding an entry involves four steps:

  • 1. Create the attributes of the entry and add them to an attribute set.
  • 2. Specify the DN of the entry to be created.
  • 3. Create an LdapEntry object with the DN and the attribute set.
  • 4. Call the LdapConnection.Add method to add it to the directory.

The C# code fragments below shows how to add an entry using Novell.Directory.Ldap namespace:

Creating an entry in the directory

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

//Creates the List attributes of the entry and add them to attribute set

LdapAttributeSet attributeSet = new LdapAttributeSet();

attributeSet.Add( new LdapAttribute( “objectclass”, “inetOrgPerson”));

attributeSet.Add( new LdapAttribute(“cn”, new string[]{“James Smith”, “Jimmy Smith”}));

attributeSet.Add( new LdapAttribute(“givenname”, “James”));

attributeSet.Add( new LdapAttribute(“sn”, “Smith”));

attributeSet.Add( new LdapAttribute(“mail”, “JSmith@Acme.com”));

// DN of the entry to be added

string dn = “cn=KSmith,” + containerName;

LdapEntry newEntry = new LdapEntry( dn, attributeSet );

//Add the entry to the directory

ldapConn.Add( newEntry );

Modifying Entry Properties

This section contains information about how to modify the attributes of an existing entry inside a directory using classes provided by Novell.Directory.Ldap namespace. To modify the attributes of an existing entry, use the Modify function of LdapConnection class.

Modifying an entry attributes involves three steps:

  • 1. Create an LdapModification object for each of the attributes to be modified using an attribute with a new value (in case of add/replace) and the modification type, for example, add, replace or delete.
  • 2. Create an LdapModification array with all the LdapModification objects created above.
  • 3. Call the LdapConnection.Modify method to modify the entry attributes

Modifying entry properties

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

ArrayList modList = new ArrayList();

String desc = “This object belongs to test user”;

// Add a new value to the description attribute

LdapAttribute attribute = new LdapAttribute( “description”, desc);

modList.Add( new LdapModification(LdapModification.ADD, attribute));

//Replace the existing email with the new email value

attribute = new LdapAttribute( “mail”, “James_Smith@Acme.com”);

modList.Add( new LdapModification(LdapModification.REPLACE, attribute));

LdapModification[] mods = new LdapModification[modList.Count];

Type mtype=Type.GetType(“Novell.Directory.LdapModification”);

mods = (LdapModification[])modList.ToArray(typeof(LdapModification));

//Modify the entry in the directory

ldapConn.Modify ( dn, mods );

Renaming an Entry

This section contains information about how to rename (or change the RDN) an entry inside a directory using classes provided by Novell.Directory.Ldap namespace. To rename an entry use the Rename function of LdapConnection class.

Renaming an entry involves two steps:

  • 1. Select the new RDN of the entry.
  • 2. Specify whether you want to keep the old name as an attribute value or not.

The C# code fragments below show how to rename an entry using Novell.Directory.Ldap namespace:

Renaming an entry in the directory

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

//Renames the entry to newRDN. If third parameter is true it means, the old name is not

//retained as an attribute value. If false, the old name is retained as an attribute value.

ldapConn.Rename(oldDN, newRDN, true);

Moving an Entry

This section contains information about how to move an entry (changing the parent DN) inside a directory from one container to another using classes provided by Novell.Directory.Ldap namespace. To move an entry use the Rename function of LdapConnection class, do the following:

  • 1. Select the new container DN (parentDN) where the entry has to be moved.
  • 2. Specify whether you want to keep the old name as an attribute value or not.

The C# code fragments below shows how to move an entry using Novell.Directory.Ldap namespace:

Moving an entry in the directory

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

//Moves the entry to new container named parentDN. If third parameter is true,

it means the old //name is not retained as an attribute value. If false, the old

name is retained as an attribute //value.

ldapConn.Rename(oldDN, oldDN,parentDN, true);

Deleting an Entry

This section contains information about how to delete an entry from the directory using classes provided by Novell.Directory.Ldap namespace. To delete an entry use the Delete function of LdapConnection class.

Deleting an entry in the directory

// C# Library namespace

using Novell.Directory.Ldap;

// Creating an LdapConnection instance

LdapConnection ldapConn= new LdapConnection();

//Connect function will create a socket connection to the server

ldapConn.Connect(ldapHost,ldapPort);

//Bind function will Bind the user object Credentials to the Server

ldapConn.Bind(userDN,userPasswd);

//Deletes the entry from the directory

ldapConn.Delete(entryDN);

 

Controls and Extensions

 

Controls and Extensions were added to the LDAPv3 protocol. In version 2, there was no standard mechanism to extend the protocol, requiring developers to extend the protocol in non-standard ways. In version 3, extensions and controls were defined to provide consistent expansion of the protocol.