Click here to Skip to main content
Licence CPOL
First Posted 5 May 2005
Views 255,627
Downloads 6,619
Bookmarked 152 times

Wrapper API for using Microsoft Active Directory Services

By | 5 May 2005 | Article
If you develop web applications with Microsoft® ASP.NET and have the need to secure your site from unauthorized access, you have surely investigated the various authentication and authorization techniques that ASP.NET 1.x enables. This article discusses how to use Microsoft Active Directory Services

Overview

Applies To

  • Microsoft® ASP.NET 1.x
  • Microsoft® Visual Studio® .NET 2003
  • Microsoft® Active Directory® Services

Summary

If you are developing web applications utilizing Microsoft® ASP.NET and have the need to secure your site from unauthorized access, you have surely investigated the various authentication and authorization techniques that ASP.NET 1.x enables. This article discusses how to use Microsoft Active Directory Services by using the developed wrapper API.

Contents

Introduction

Active Directory provides the ability to authenticate and authorize users from a centralized location, so users don't need to remember the password for every application, if they use Active Directory for authentication. Microsoft uses Active Directory in almost all of their application servers like Microsoft Content Management Server, Microsoft Share Point Portal Server, Microsoft CRM, and Microsoft Exchange Server etc., for centralized authentication and authorization purposes. As Active Directory is integrated with the Windows Operating System, very intrinsic support is available at a very low level.

The Active Directory Services Interface (ADSI) has always been a very effective way of dealing with users in a Windows network. The System.DirectoryServices namespace gives users access to some rudimentary user administration via ASP.NET. ADSI classes in the DirectoryServices namespace enables programmers to access ADSI objects using the System.DirectoryServices namespace.

How does Active Directory work?

Active Directory is simply a hierarchical, object-oriented database that represents all of your network resources. At the top, there's typically the Organization (O), beneath that Organizational Units (OU) as containers, and finally, objects that consist of your actual resources. This hierarchical format creates a very familiar and easy-to-administer tree for systems administrators. For example, if you assign an OU access to a given resource, that access will also be persisted to the objects that are contained within it.

What is this article about?

Active Directory Services is a bit complex, so to make it more user friendly, I created a wrapper API in VB.NET and C# .NET, which performs all the operations a developer needs in order to navigate the Active Directory.

By using the wrapper API, the developer can do the following operations:

  • Add User to Group
  • Create Active Directory Group
  • Create Active Directory User
  • Delete Active Directory Group Account
  • Delete Active Directory User Account
  • Enable Active Directory User Account
  • Does Group Exist
  • Is User Valid
  • Load All Users
  • Load All Groups
  • Load Group
  • Load User
  • Login
  • Remove User From Group
  • Does User Exist
  • Set Password
  • Update User
  • Update Group

What's inside the wrapper API for Active Directory?

As shown in the Figure 1, the wrapper API consists of the following classes:

ADManager class

ADManager is a singleton class responsible for managing the users and groups in the Active Directory.

How to use

To add a user to a particular Active Directory group, the following code will be used:

Dim _ADUser As ADUser
_ADUser = ADManager.Instance.LoadUser("adnan")
Dim _ADGroup As ADGroup
_ADGroup = ADManager.Instance.LoadGroup("DeveloperGroup")
ADManager.Instance.AddUserToGroup(_ADUser.DistinguishedName, 
    _ADGroup.DistinguishedName)

To check whether a user exists in Active Directory, the following simple code will be used:

If ADManager.Instance.UserExists("adnan") Then
    MsgBox("User Exist in the Active Directory")
End If

ADGroup class

The ADGroup class consists of properties and methods responsible for dealing with Active Directory groups.

I map the following properties with an Active Directory Group in order to make the properties simple.

  • "Name" mapped with "cn"
  • "DisplayName" mapped with "DisplayName"
  • "DistinguishedName" mapped with "DistinguishedName"
  • "Description" mapped with "Description"

How to use

The ADGroup class is used to create/update groups in the Active Directory. Shown below is a code snippet for creating a group in Active Directory:

Dim _AdGroup As New ADGroup
_AdGroup.Name = "DeveloperGroup"
_AdGroup.Description ="All developers in the company"
_AdGroup = ADManager.Instance.CreateADGroup(_AdGroup) 

ADUser class

The ADUser class consists of properties and methods responsible for dealing with Active Directory users. The ADUser properties and the corresponding property in the Active Directory are given below:

  • "FirstName" mapped with "givenName"
  • "MiddleInitial" mapped with "initials"
  • "LastName" mapped with "sn"
  • "UserPrincipalName" mapped with "UserPrincipalName"
  • "PostalAddress" mapped with "PostalAddress"
  • "MailingAddress" mapped with "MailingAddress"
  • "ResidentialAddress" mapped with "HomePostalAddress"
  • "Title" mapped with "Title"
  • "HomePhone" mapped with "HomePhone"
  • "OfficePhone" mapped with "TelephoneNumber"
  • "Mobile" mapped with "Mobile"
  • "HomePhone" mapped with "HomePhone"
  • "Fax" mapped with "FacsimileTelephoneNumber"
  • "Email" mapped with "Email"
  • "Url" mapped with "Url"
  • "UserName" mapped with "sAMAccountName"
  • "DistinguishedName" mapped with "DistinguishedName"
  • "IsAccountActive" to check the user status in the Active Directory

How to use

  1. The ADUser class is used to create a user. The code snippet to create the user is given below:
  2. Dim _AdUser As New ActiveDirectory.ADUser
    _AdUser.FirstName = "Syed"
    _AdUser.MiddleInitial = "Adnan"
    _AdUser.LastName = "Ahmed" '
    _AdUser.Email = "adnanahmed235@yahoo.com"
    _AdUser.UserName = "adnan"
    _AdUser.Password = "123456"
    _AdUser.IsAccountActive = True
    _AdUser.MailingAddress = "Riyadh, Saudi Arabia"
    _AdUser.Title = "Software Engineer"
    _AdUser = ADManager.Instance.CreateADUser(_AdUser)
  3. If you want to update the user in the Active Directory, use the following code snippet:
  4. Dim _AdUser As ADUser
    _AdUser = ADManager.Instance.LoadUser("adnan")
    _AdUser.MailingAddress = "Jeddah, Saudi Arabia"
    _AdUser.Title = "Senior Software Engineer"
    _AdUser.Update()
  5. You can use the ADUser class to reset the user password.
  6. Dim _AdUser As ADUser
    _AdUser = ADManager.Instance.LoadUser("adnan")
    _AdUser.SetPassword("654321")

Utility class

The Utility class is responsible for the general options.

Configuration changes

Before using the wrapper API, you have to follow the instructions below for Windows and web based applications.

Web based applications

Add the following line inside the <system.web> tag in the web.config file:

<identity impersonate="true" />

Add the following lines inside the <appSettings> tags:

<add key="Domain" value="MyDomain.com" />
<add key="ADPAth" value="LDAP://MyDomain " />
<add key="ADUser" value="administrator" />
<add key="ADPassword" value="123" />
<add key="ADUsersPath" value="OU=DeveloperDepartment," />

Note: Here in the 'ADUsersPath' key, the value ("OU=DeveloperDepartment,") shows the OU= Organizational Unit in the Active Directory as an example. You can write any of your organizational units or create a new one for testing.

Go to IIS, select the website, in the Properties window, select the Directory Service tab, in Authentication and Access Control Options, click the Edit button. It will open the Authentication Methods window, select Anonymous Access and enter the Domain Administrator Account user name and password, and select Integrated Windows Authentication, as shown in the following figures:

Figure #2

Figure #3

Windows based applications

Add the following lines inside the <configuration> tags:

<appSettings>
<add key="Domain" value="MyDomain.com" />
<add key="ADPAth" value="LDAP://MyDomain " />
<add key="ADUser" value="administrator" />
<add key="ADPassword" value="123" />
<add key="ADUsersPath" value="OU=DeveloperDepartment," />
</appSettings>

Note: A sample App.config file is included in the download file.

Platforms tested

I have tested the project on the following platforms:

  • Windows Server 2003
  • Windows XP SP1 or SP2

Conclusion

I have demonstrated how easy it is to navigate Active Directory objects by using the wrapper API which uses System.DirectoryServices. In the next release of my wrapper API, I will demonstrate how to manage Active Directory Roles and Permissions by using the wrapper API. I have given the API in both VB.NET and C#.NET, and you can use it in both Windows and web based applications.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Syed Adnan Ahmed

Architect
Version 1
Ireland Ireland

Member

Adnan Ahmed is SharePoint Architect in Version 1(http://www.version1.com), the IT Consulting Company in Ireland and has involved with many large enterprises to help them realise real benefits of SharePoint 2007|2010.
 
SharePoint Architect | Blogger | IT Evangelist | MCPD SharePoint 2010 Developer| MCITP SharePoint Administrator 2010
 
Email: adnan.ahmed@live.ie
Owner: http://www.mossgurus.com
http://www.sp-blogs.com
Linked In Profile: http://www.linkedin.com/in/syedadnanahmed
 
My Blogs:
http://www.sp-blogs.com/blogs/adnan

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionWho here thinks this design is a bad idea? PinmemberMike Niccum5:20 13 Jul '06  
AnswerRe: Who here thinks this design is a bad idea? Pinmembertroybooth2:52 17 Mar '11  
QuestionCan I do this? PinmemberHZ_798:04 5 Jul '06  
Questionhow to use this code in my .net project Pinmemberiamjagadishv1:28 8 Jun '06  
AnswerRe: how to use this code in my .net project Pinmemberjamesvickers4:12 8 Jun '06  
Question? Can I access LDAP of Solaris environment from my Windows environment. PinmemberSuranjan Nandi3:16 9 Mar '06  
GeneralContribute to Syed (LogonHours) PinmemberSpyram7:24 2 Mar '06  
Hi Syed,
 
I've expanded your work and used it in a project. So I have this humble contribute that I coded for using LogonHours attribute.
 
      /// <summary>
      /// This class manages the Logon Hours attribute in AD.
      ///
      /// The structure is a byte[].lenght = 21 and it stores data for each slot (hour/day) in it
      ///      This is done using power of 2 conversion (2^byte position)
      /// ie.: Sunday 0h00      = byte[00], first bit = 1 (2^0)
      ///         Sunday 1h00      = byte[00], second bit = 2 (2^1)
      ///         (...)
      ///         Sunday 8h00      = byte[01], first bit = 1 (2^1)
      ///         (...)
      ///         Saturday 23h00 = byte[20], eighth bit = 128 (2^7)
      /// A user with access to sunday 0h00, 1h00, 3h00 and 7h00
      ///      whould have at [0] the value = 2^0 + 2^1 + 2^3 = 1 + 2 + 8 + 128 = 139
      ///
      /// PS: each day needs to have 3 bytes (from 0h to 07 from 8h to 15h and from 16h to 23h)
      ///      Thats why Sunday starts at [0], Monday at [3], etc.
      ///
      /// A user with a value of 255 in all byte[] array means it has access everytime
      /// A user with a value of   0 in all byte[] array means it has access denied
      /// </summary>
     
      public class ADLogonHours
      {
            #region Private data
 
            byte[] _logondata;
            private int basesize = 21;
            private byte[] AllFree = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 };
            private byte[] AllBlock = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            private static ADLogonHours _instance;
 
            #endregion
 
            #region Public Properties
 
            public byte[] Value
            {
                  get
                  {
                        return _logondata;
                  }
            }
            public enum ADDay
            {
                  Sunday = 0,
                  Monday = 3,
                  Tuesday = 6,
                  Wednesday = 9,
                  Thursday = 12,
                  Friday = 15,
                  Saturday = 18
            }
            public enum ADHour
            {
                  H00 = 0,
                  H01 = 1,
                  H02 = 2,
                  H03 = 3,
                  H04 = 4,
                  H05 = 5,
                  H06 = 6,
                  H07 = 7,
                  H08 = 10,
                  H09 = 11,
                  H10 = 12,
                  H11 = 13,
                  H12 = 14,
                  H13 = 15,
                  H14 = 16,
                  H15 = 17,
                  H16 = 20,
                  H17 = 21,
                  H18 = 22,
                  H19 = 23,
                  H20 = 24,
                  H21 = 25,
                  H22 = 26,
                  H23 = 27
            }
 
            #endregion
           
            #region Constructors
            internal ADLogonHours()
            {
                  _logondata = new byte[basesize];

            }
 
            public ADLogonHours(byte[] aLogonHourData)
            {
                  if (aLogonHourData.Length != basesize)
                        new ApplicationException("ERROR: Not a valid Logon Hour Byte Array. Should be " + basesize);
                  _logondata = aLogonHourData;
            }
 
            public static ADLogonHours Instance
          {
               get{
                    if (_instance == null)
                    {
                              _instance = new ADLogonHours();
                    }
                    return _instance;
               }
            }
            #endregion
 
            #region Public Action Functions
 
            public void DenyAll()
            {
                  for (int a = 0; a <= 20; a++)
                        _logondata[a] = 0;
            }
            public void AllowAll()
            {
                  for (int a = 0; a <= 20; a++)
                        _logondata[a] = 255;
            }
            public void AllowSlice(ADDay aDay, ADHour aHour)
            {
                  int index = (int)aDay + GetDayFactor(aHour);
                  if(IsSliceAllowed(aDay, aHour))
                        _logondata[index] += (byte)GetDaySliceValue(aHour);
            }
            public void DenySlice(ADDay aDay, ADHour aHour)
            {
                  int index = (int)aDay + GetDayFactor(aHour);
                  if (!IsSliceAllowed(aDay, aHour))
                        _logondata[index] -= (byte)GetDaySliceValue(aHour);
            }

            #endregion
 
            #region Public boolean Funtions
 
            public bool IsSliceAllowed(ADDay aDay, ADHour aHour)
            {
                  int index = (int)aDay + GetDayFactor(aHour);
                  int value = GetDaySliceValue(aHour);
                  if (value == 0) //If value = 0 then its not alloed
                        return false;
                  else if(_logondata[index] % value != 0) //If remaining from div != 0 then its not a POW(2) so its not allowed
                        return false;
                  else
                        return true;//If it's 0 then % value = 0 meaning the POW 2 is there =>Allowed
            }
            public bool IsCompletlyFree()
            {
                  if (CompareByteArray(_logondata, AllFree))
                        return true;
                  else return false;
            }
            public bool IsCompletlyRestricted()
            {
                  if (CompareByteArray(_logondata, AllBlock))
                        return true;
                  else return false;
            }
           
            #endregion
 
            #region Private Functions
 
            private int GetDayFactor(ADHour aHour)
            {
                  return (int)aHour / 10;
            }
            private int GetDaySliceValue(ADHour aHour)
            {
                  return (int)(Math.Pow(2, ((int)aHour % 10)));
            }
            private bool CompareByteArray(byte[] array1, byte[] array2)
            {
                  if (array1.Length != array2.Length)
                        return false;
                  for (int a = 0; a < array1.Length; a++)
                        if (array1[a] != array2[a])
                              return false;
                  return true;
            }
 
            #endregion
 
      }
 
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
//How to use this in this API
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
 
//in utility class....
internal static void SetPropertyByte(DirectoryEntry oDE, string PropertyName, byte[] PropertyValue)
            {
                  if (PropertyValue != null)
                  {
                        if (oDE.Properties.Contains(PropertyName))
                        {
                              oDE.Properties[PropertyName][0] = PropertyValue;
                        }
                        else
                        {
                              oDE.Properties[PropertyName].Add(PropertyValue);
                        }
                  }
            }
 
            internal static byte[] GetPropertyByte(DirectoryEntry oDE, string PropertyName)
            {
                  if (oDE.Properties.Contains(PropertyName))
                  {
                        return (byte[])oDE.Properties[PropertyName][0];
                  }
                  else
                  {
                        byte[] abyte = new byte[1];
                        return abyte;
                  }
            }
 
//in ADUSER class....
 
public DirectoryEntry UpdateDirectoryEntry(DirectoryEntry de)
            {
                  (...)
                 
                  if(!LogonHours.IsCompletlyFree()) //Only set value if it has restrictions
                        Utility.SetPropertyByte(de, "logonHours", LogonHours.Value);
                  return de;
            }
 
//And thats all. Please make sure you create propertyes in your AADUser class.
 
Ricardo Silva
 
-- modified at 7:13 Friday 5th May, 2006
GeneralError in GetUser (interop) Pinmemberflanderp4:01 6 Jan '06  
GeneralRe: Error in GetUser (interop) Pinmemberfuhan22:28 2 May '06  
QuestionCannot use the wrapper API. PinmemberOrris5:17 16 Dec '05  
GeneralSetPasswor error Pinmemberfresht21:04 13 Dec '05  
GeneralAddUser Error - SetPassword PinmemberLuciana Perez8:19 7 Dec '05  
GeneralRe: AddUser Error - SetPassword Pinmemberdavenaylor20001:19 29 Mar '06  
GeneralRe: AddUser Error - SetPassword Pinmemberkbomb98710:08 29 Mar '06  
GeneralRe: AddUser Error - SetPassword Pinmemberkaipira7:45 14 Jan '10  
QuestionHow to enable / disable a computer account? Pinmembernetyale11:05 2 Dec '05  
GeneralCreate Exchange Mailbox Pinmemberrenek20035:59 28 Nov '05  
Generalbug on ADManager.Login() PinmemberYaniv Karta23:10 30 Oct '05  
QuestionCompany property for the user PinmemberNagalingam9:42 28 Oct '05  
GeneralAdd new user to existing group error's Pinmemberhellow-world22:27 26 Oct '05  
GeneralRe: Add new user to existing group error's Pinmemberkunal197913:37 28 Jun '07  
GeneralexamplePlease PinmemberjamesVazquez5:36 25 Oct '05  
GeneralexamplePlease !!!! PinmemberjamesVazquez5:35 25 Oct '05  
GeneralJamesVazquez PinmemberjamesVazquez5:34 25 Oct '05  
GeneralAduser.groups PinmemberJamesVazquez11:07 24 Oct '05  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120529.1 | Last Updated 6 May 2005
Article Copyright 2005 by Syed Adnan Ahmed
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid