Online Shopping : Computers : Programming : Languages : C#

+ Search
Add Entry AlertManage Folder Edit Entry Add page to http://del.icio.us/
Did You Find This Entry Useful?

47 of 124 people (38%) answered Yes
Recently 8 of 10 people (80%) answered Yes

Entry

Using ADSI in ASP.Net, how confirm group membership for an authenticated NT.

Jul 30th, 2009 23:09
s3odia s3odia, chat alarab, Taksh Verdhan, Deepak Sharma, Tom Regan, chat


ADSI stands for Active Directory Server Interface
and it basically is a com object to allow ASP (or any language) to
interogate and manipulate the types of objects that are managed by
servers traditionally: domains, groups, users, passwords, directory
listings. It is intended to be an integral part of NT5 and will also be
extended to communicate with a variety of server architectures (Netscape
Servers, Novell servers, LDAP servers)
ADSI Section
ADSI abstracts the capabilities of directory services from different
network providers to present a single set of directory service
interfaces for accessing and managing network resources. Administrators
and developers can use ADSI services to enumerate and manage resources
in a directory service, no matter which network environment contains the
resource. This can be an LDAP-based, NDS-based, or NTDS-based directory.
The System.DirectoryServices namespace gives users access to some
rudimentary user administration via ASP.NET. This article first reviews
what the Active Directory (AD) is, then looks briefly at the actual
System.DirectoryServices namespace itself, and finally presents the code
that allows us to add, edit, and delete users. 
What Is the Active Directory?
In today's networked environment it's crucial to be able to control
access to each network device easily. A method is needed to control who
has access to what device and when. This includes devices such as
printers, files, and any other local network resource or item on the
distributed network. AD provides the ability to do this, integrated with
the operating system (OS), which means very intrinsic support at a very
low level. 
How Does AD Work?
AD is simply a hierarchical, object-orientated 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-administrate
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. 
How Can We Access the AD?
Within the .NET Framework we are provided with the
System.DirectoryServices namespace, which in turns uses Active Directory
Services Interfaces (ADSI). If you have Microsoft Help installed with
the .NET Framework Class library, you can refer to the following URL:
ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemDirectoryServices.htm. 
If not, take a look on MSDN directly:
http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemDirectoryServices.asp
ADSI is a way to interact with many different directory services
providers through code, a programmatic interface. The classes in the
System.DirectoryServices namespace can be used with any of the Active
Directory service provider listed below: 
Directory Service Providers	Path	
Windows NT version 5.0, Windows 2000, or Windows XP	WinNT://path	
Lightweight Directory Access Protocol (LDAP)	LDAP://path	
Novell NetWare Directory Service	NDS://path	
Novell Netware 3.x	NWCOMPAT://path	
Internet Information Services (IIS)	IIS://	
Figure 1.1 AD Service Providers 
Within the System.DirectoryServices namespace there are two main
classes: the System.DirectoryServices.DirectoryEntry and the
System.DirectoryServices.DirectorySearcher classes. This article will
not covering these in detail because the information can be read on MSDN
using the above URLs. Also, note the DirectorySearcher class only works
with the LDAP provider. 
Also, when using DirectoryEntry objects, there is a schema for each
object. A schema is the type of entry that object is. For example, if
you had a DirectoryEntry object with a "User" schema, it would represent
a user. 
For this article, we will use the Windows 2000 Provider (WinNT://) and
the System.DirectoryServices.DirectoryEntry class. 
User Administration
This article's example involves creating a Data Access Layer (DAL) to
wrap around the System.DirectoryServices namespace to allow us to
perform very rudimentary user administration tasks. A DAL is a method
for abstracting the actual complexities of performing the data access.
For example, if you were to write a DAL around an Access database, you
could hide all work with ADO.NET within your objects, and when a
developer needs to perform any database interaction, the developer
simply needs to use these objects and not worry about specific
implementation details. The developer never has to worry about ADO.NET
(or even SQL). If you decide later to upgrade to Microsoft SQL Server,
you simply need to change the DAL to work with the new database, and
nothing else needs to be changed. For more information, read my
introduction to N-Tier Application Architecture at
http://www.15seconds.com/Issue/011023.htm. 
The User Object 
The first object we will create is used to represent the current state
of any given User. This object abstracts the "User" DirectoryEntry
class, and it resembles a more tangible User object for clarity. 
namespace DSHelper {
  public class DSUser {
    public DSUser(System.DirectoryServices.DirectoryEntry user) {
      this.domainName=user.Path;
      this.Username=user.Name;
      this.Password=user.Password;
      try {
        this.FullName=Convert.ToString(user.Invoke("Get", new object[]
{"FullName"}));
        this.Description=Convert.ToString(user.Invoke("Get", new
object[] {"Description"}));
        this.PasswordExpired=Convert.ToInt32(user.Invoke("Get", new
object[] {"PasswordExpired"}));
        this.RasPermissions=Convert.ToInt32(user.Invoke("Get", new
object[] {"RasPermissions"}));
        this.MaxStorage=Convert.ToInt32(user.Invoke("Get", new object[]
{"MaxStorage"}));
        this.PasswordAge=Convert.ToInt32(user.Invoke("Get", new object[]
{"PasswordAge"}));
        this.HomeDirectory=Convert.ToString(user.Invoke("Get", new
object[] {"HomeDirectory"}));
        this.LoginScript=Convert.ToString(user.Invoke("Get", new
object[] {"LoginScript"}));
        this.HomeDirDrive=Convert.ToString(user.Invoke("Get", new
object[] {"HomeDirDrive"}));
        this.userDirEntry=user;
      }catch(Exception e) {
        throw(new Exception("Could not load user from given
DirectoryEntry"));
      }
    }
    public DSUser(string Username, string Password, string DomainName) {
      domainName=DomainName;
      if(domainName=="" || domainName==null)
domainName=Environment.MachineName;
      username=Username;
      password=Password;
    }
    private object groups=null;
    public object Groups{get{return groups;} set{groups=value;}}
//I removed the public getters and setters, and the private member variables
//download the full source for a complete listing
  }
}
Figure 1.2 The User Object 
Our user object has two default constructors. The first is used to
initialize our user with a given DirectoryEntry object. It will attempt
to use the Invoke method to "Get" the user's properties off of the object. 
The second constructor is used to create a new user. You only need to
pass in the three required parameters and it will create a new DSUser
object for you to use when creating a new user. Notice that no AD work
is being done, thus no real user in your AD is being created yet. 
The DAL 
The next step is to create the DAL wrapper for AD. The next few sections
of code will be a break down of the entire UserAdmin DAL. For a complete
listing please see the downloadable source ZIP file. 
We first create and initialize the variables we will need in our
UserAdmin class: 
    #region default property initialization
    //our error logging device.  keeping it simple to avoid bloating
sample code
    System.Text.StringBuilder errorLog = new System.Text.StringBuilder();
    private System.Boolean error=false;
    public System.Boolean Error{get{return error;}}
    public string ErrorLog{get{return errorLog.ToString();}}
    //setup default properties
    private string loginPath="WinNT://"+Environment.MachineName+",computer";
    private string domainName=null;
    private string loginUsername=null;
    private string loginPassword=null;
    private System.DirectoryServices.AuthenticationTypes
authenticationType = System.DirectoryServices.AuthenticationTypes.None;
    private System.DirectoryServices.DirectoryEntry AD=null;
    private System.Boolean connected=false;
    #endregion
Figure 1.3 Default Property Initialization 
Notice how we hardcode the LoginPath to be the WinNT provider. This
would need to be changed in order for this DAL to work with any of the
other AD service providers. Also notice that I simplified error handling
by using a System.Text.StringBuilder object to hold the error log. In
the case of an error, it will simply be appended to this, and the
"error" Boolean variable will be set. 
The next region of code to examine is the constructors for our class
(see Figure 1.4, below). 
    #region .ctor's
    public UserAdmin() {
      Connect();
    }
    /// <summary>
    /// Allows you to create a UserAdmin class specifying all
credentials, if needed
    /// </summary>
    /// <param name="LoginUsername"></param>
    /// <param name="LoginPassword"></param>
    /// <param name="AuthenticationType"></param>
    /// <param name="DomainName"></param>
    public UserAdmin(string LoginUsername, string LoginPassword,
System.DirectoryServices.AuthenticationTypes AuthenticationType, string
DomainName) {
      loginUsername=LoginUsername;
      loginPassword=LoginPassword;
      authenticationType=AuthenticationType;
      if(DomainName=="" || DomainName==null)
DomainName=System.Environment.UserDomainName;
      domainName=DomainName;
      Connect();
    }
    /// <summary>
    /// An alternative way to get a UserAdmin class which allows you to
specify an alternative LoginPath, for example LDAP, or IIS
    /// </summary>
    /// <param name="LoginPath"></param>
    /// <param name="LoginUsername"></param>
    /// <param name="LoginPassword"></param>
    /// <param name="AuthenticationType"></param>
    /// <param name="DomainName"></param>
    public UserAdmin(string LoginPath, string LoginUsername, string
LoginPassword,
System.DirectoryServices.AuthenticationTypes AuthenticationType, string
DomainName) {
      loginPath=LoginPath;
      loginUsername=LoginUsername;
      loginPassword=LoginPassword;
      authenticationType=AuthenticationType;
      if(DomainName=="" || DomainName==null)
DomainName=System.Environment.UserDomainName;
      domainName=DomainName;
      Connect();
    }
    #endregion
Figure 1.4 Constructors 
The first constructor provided is the basic default constructor. This
allows our object to be created without any given parameters. Since
there are no required pieces of data needed, we allow for this. This
means that the object will connect to the local WinNT provider with no
special security privileges. 
The second constructor provided allows us to specify the login
credentials for connecting to the AD for the given domain. This is handy
when you want to connect to the any domain with the given credentials. 
Finally, the last constructor adds the LoginPath parameter. This allows
you to override the LoginPath, which gives you the ability to choose an
alternative service provider than the default. 
The next region of code is what I call the "Action Methods." There is
actually quite an abundance of code so please refer to the downloadable
source file while covering these methods. The methods in order are as
follows: 
public DSHelper.DSUser LoadUser(string username)
This method is used to take a username and find its DirectoryEntry in
the current provider. If it cannot be found, it will simply return null. 
public System.Boolean AddUserToGroup(string groupname, DSHelper.DSUser
dsUser) 
This method takes the name of an existing group and an instance of our
custom DSUser class, and makes an attempt to add that user to the group
provided. This is the first method where we can see impersonation being
done. (Impersonation is discussed near the end of this article.) The
core to this method is the line: 
public System.Collections.ArrayList GetGroups()
We will use this method to get a list of all the groups in our domain
for the given provider. 
public System.Boolean DeleteUser(DSHelper.DSUser dsUser)
This will take the given user and completely delete it out of the Active
Directory. 
public System.DirectoryServices.DirectoryEntry SaveUser(DSHelper.DSUser
dsUser)
No DAL would be complete without the ability to actually Save and Insert
new records into our datastore. This method does both. It will first
check the AD to see if the specified user exists or not. If it does, it
will attempt to update it with the settings you provide to it. If the
user does not exist, it will attempt to create it. 
public System.Boolean Connect()
Lastly we need a way to connect to our datastore. The Connect() method
is used to do just that. Notice, however it does not perform any
impersonation or specify any credentials to use the AD at this point. We
only provide credentials when we need too. This allows the DAL to be
used in a Read-Only state when no credentials have been provided. 
With that high-level overview, let's put it all together and see just
how we would use this DAL to create an ASP.NET user administration page. 
ASP.NET User Admin Page 
This sample project gives you the ability to enter in the username and
password for the administrator user (to actual make changes you will
need to supply these credentials). It also offers a a text box so you
can enter in the name of any user account for the domain, and it will
list its properties. You can edit and save, or delete that user entirely. 
Take time now to review the project in detail. Concentrate on the
portions where we interact with the DAL. Think of new ways to improve
the interface, and actually create a more useable and friendly design.
Also consider how you could create an interface using a console
application. 
Impersonation 
The word "Impersonation" indicates that we can act as another person or
user. Within our ASP.NET applications this means we can temporarily act
as another user, instead of the ASP.NET user that is the default account
that IIS uses for anonymous access. We want to be able to allow our code
to impersonate another user that has greater access to the AD resource
that we need to modify. This is accomplished fairly easily. 
    #region setup impersonation via interop
    //need to import from COM via InteropServices to do the
impersonation when saving the details
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_PROVIDER_DEFAULT = 0;
    System.Security.Principal.WindowsImpersonationContext
impersonationContext; 
[DllImport("advapi32.dll", CharSet=CharSet.Auto)]public static extern
int LogonUser(String lpszUserName,
String lpszDomain,String lpszPassword,int dwLogonType,int
dwLogonProvider,ref IntPtr phToken);
[DllImport("advapi32.dll",
CharSet=System.Runtime.InteropServices.CharSet.Auto,
SetLastError=true)]public
extern static int DuplicateToken(IntPtr hToken, int impersonationLevel,
 ref IntPtr hNewToken);
    #endregion
Figure 1.5 Setting Up Impersonation 
First we need to import a few methods from the advapi32.dll, including a
couple of helpful constants. The variable named impersonationContext
will be used to hold the Windows user prior to the impersonation operation. 
Now that we have set up impersonation, here is an example of how to use it: 
if(impersonateValidUser(this.LoginUsername, this.DomainName,
this.loginPassword)) {
  //Insert your code that runs under the security context of a specific
user here.
  //don't forget to undo the impersonation
   undoImpersonation();
} else {
  //Your impersonation failed. Therefore, include a fail-safe mechanism
here.
}
Figure 1.6 Using Impersonation 
We simply need to call the impersonateValidUser method with the valid
username and password to impersonate that user. From then on, until
undoImpersonation() is called, we will be performing all actions as the
supplied username. 
Authors Note: To get impersonation working correctly under asp.net,
change the processModel node in the machine.config
(c:\winnt\microsoft.net\framework\v%VERSION%\config\machine.config). The
username attribute must be set to "System". For more information
regarding this change please see the security documentation on MSDN. 
Conclusion
These are the essentials needed to complete a fully functioning DAL for
AD for any provider with the System.DirectoryServices namespace. Take
this sample, and adapt it to your needs. Keep in mind that for any DAL
to be really useful, it should not be limited to a single provider,
including your normal database providers. You should be able to swap in
a DAL that uses SQL Server to maintain the database of users and remove
the AD DAL entirely. 
There are a few more portions you may want to complete. This includes
adding the functionality for Group administration. This would permit
creating, editing, and deleting groups, and listing groups for each
user, and listing the users within each group, etc. Unfortunately most
of this functionality would need to be done via COM Interop (see
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcongeneralconsiderations.asp).
For more information on how to do this, refer to the More Info.txt file
included in the downloadable ZIP file accompanying this article.
In the next part of the code we instantiate an ADO Connection object,
and set it's provider to: ADSDSOObject. 
Set Conn = CreateObject("ADODB.Connection")
Conn.Provider = "ADSDSOObject"
Since I have setup my Site Server LDAP database to disallow Anonymous
acces, I am going to do an authenticated bind. Doing an authenticated
bind to an LDAP Server requires that the user you are binding to has
Administrative rights. The Administrator password must also be supplied. 
Conn.Open "ADs Provider", _
          "cn=Administrator,ou=members,o=microsoft", _
          "secret"
Once the connection has been suceesfully opened, the SQL Statement is
executed creating an ADO recordset. 
Set rs = Conn.Execute(SQLStmt)
And if all goes well, we should be able to iterate the ADO recordset.
The Property Value is usually returned as a Variant. So keep in mind,
that some items may be returned as Arrays. Generally single value
properties are Strings and multi-value properties are Arrays. 
Do While Not rs.EOF Or rs.BOF
   ReturnValue = rs.Fields(0)
   If IsArray(ReturnValue) Then
        For I = LBound(ReturnValue) To UBound(ReturnValue)
            If ReturnValue(I) <> "" Then
                Response.Write ReturnValue(I) & "<BR>"
            End If
        Next
   Else
        Response.Write ReturnValue & "<BR>"
   End If
   rs.MoveNext
Loop
Hope you find it useful
Deepak
http://www.s3odia.com/f1/
http://www.s3odia.com/f2/
http://www.s3odia.com/f3/
http://www.s3odia.com/f4/
http://www.s3odia.com/f5/
http://www.s3odia.com/