Q&A
Activate Windows Impersonation Selectively
Activate Windows impersonation selectively, determine the update order of DataTables at run time, guarantee the delivery order of asynchronous delegates, and more.
Technology Toolbox: C#, SQL Server 2000, ASP.NET
Q:
Turn On Windows Impersonation
Can you activate Windows impersonation selectively in ASP.NET?
A:
Turning on Windows impersonation in an ASP.NET application produces a global effect: Requests sent to all pages of the application run under the identity of the user posting the request to the Web server through his browser.
You can activate impersonation successfully by specifying Basic or Integrated in the IIS security settings for the ASP.NET application and placing the <identity impersonates="true"/> element in the web.config file.
However, sometimes you don't want to turn on impersonation for all pages or for all the execution scope of a Web request. For example, you might want to access the file system during a page-processing event using the user's identity. At the same time, you might want to continue accessing a SQL Server database with integrated security using the identity of the ASP.NET worker processthat is, the identity Windows uses to perform access checks when impersonation is turned off.
Fortunately, you can easily turn impersonation on and off programmatically during the code execution flow. First remove the <identity impersonate="true"/> element from the web.config file, then switch to the caller identity programmatically. Pick up WindowsPrincipal from the Thread.CurrentPrincipal method. Extract WindowsIdentity from it and call its Impersonate method. The call executes under the context of the caller from now on. You can switch back to the ASP.NET identity by calling the Undo method on the WindowsImpersonationContext object returned from the Impersonate method:
WindowsPrincipal wp =(WindowsPrincipal)
Thread.CurrentPrincipal;
WindowsIdentity wi = (WindowsIdentity)
wp.Identity ;
WindowsImpersonationContext wc =
wi.Impersonate ();
// this code accesses file system under
// the caller identity
...
wc.Undo ();
// the code below accesses a SQL Server
// database under ASP.NET process
// identity
...
You do have an alternative to using Thread.CurrentPrincipal to extract WindowsPrincipal: You can call the Page User property:
WindowsPrincipal wp =
(WindowsPrincipal)this.User ;
Using Thread.CurrentPrincipal provides more flexibility, because it lets you switch to the caller identity even when the code is executing inside other classes the Page object has called into.
Q:
Achieve Impersonation With Forms Authentication
There must be a way to let forms-authentication-based ASP.NET applications take advantage of Windows impersonation. Any advice?
A:
The last Q&A showed how you can activate Windows impersonation in an ASP.NET application both declaratively and programmatically. In both cases, you still need to set the IIS authentication option to Basic or Integrated.
You might think this means forms-authentication-based ASP.NET applications can't take advantage of Windows impersonation, because they require you to set the IIS authentication option to Anonymous. Fortunately, this is not completely trueyou can achieve Windows impersonation in the context of a forms-authentication-based solution. You just need to take a few extra steps.
You invoke forms authentication in the usual way by mandating it and at the same time denying access to unauthenticated users in your web.config file:
<configuration>
<system.web>
<authentication mode="Forms">
<forms name="401kApp"
loginUrl="logon.aspx"/>
</authentication>
<authorization>
<deny users="?" />
Write your authentication code so that when the user clicks on the Submit button on the login page, the code checks the provided credentials against the Windows system itself instead of a custom user store. Your implementation employs the LogonUser API, which requires a username, a domain/machine name (to know what Windows users store the query), and a password. This function also requires a security provider identifier (pass 0 to use the default) and a logon type (use 3, which stands for LOGON32_LOGON_NETWORK_CLEARTEXT; see Listing 1). The LogonUser method returns a user token, which you can use as a constructor parameter when creating a WindowsIdentity in the next step of the authentication phase:
IntPtr token2 = new IntPtr(l_token1);
WindowsIdentity l_Wid = new
WindowsIdentity (token2);
Next, create a WindowsPrincipal that provides the new Windows identity, and attach it to the current HttpContext and the Thread.CurrentPrincipal:
WindowsPrincipal wp = new
WindowsPrincipal (l_Wid);
Thread.CurrentPrincipal = wp;
//HttpContext.User and Page.User point
//to the same Principal object
HttpContext.Current.User = wp;
You still need to provide a way to attach the WindowsPrincipal object on subsequent requests to Thread.CurrentPrincipal and current HttpContext when using Windows authentication. You are an authenticated user as far as the forms authentication infrastructure is concerned. But only a GenericPrincipal object is attached when the next page request from the same user hits the browser. The GenericPrincipal object contains a FormsIdentity object with the Name property set. The username is the only piece of data the forms authentication mechanism keeps across requests in its default configuration.
You have two options here. First, you can store the WindowsPrincipal object at session level in the authentication phase. The program picks up this object in the PreRequestHandlerExecute upon subsequent requeststhe event that fires right before the page starts processing the requestsand then stuffs it in Thread.CurrentPrincipal and HttpContext.Current.User (see Listing 2).
Second, you can store the user password in the UserData section of the FormsAuthenticationTicket (the cookie issued by the forms authentication mechanism is encrypted and protected against tampering). You extract the password from the cookie on each subsequent request in the AuthenticateRequest event and provide ittogether with the username (picked up from the FormsIdentity object)to the LogonUser API.
At this point, you proceed as you did during the authentication process. You create a WindowsIdentity and a WindowsPrincipal. You stuff the latter into Thread.CurrentPrincipal and HttpContext.Current.User. I didn't use the AuthenticateRequest event in the previous approach, because the session state hasn't been re-created when the AuthenticateRequest event fires.
Remember that the Windows identity calling LogonUser (the ASP.NET process identity) requires you to assign appropriate Windows permissions. You need TCB privileges under Win2K, with the worker process acting as part of the operating systemquite a powerful permissionwhile Windows XP and Windows Server 2003 relax the requirements somewhat.
Q:
Set the Update Order
How do I determine the update order of DataTables at run time?
A:
You can run into problems when committing DataSet changes in complex database situations. Take care when the DataSet contains multiple tables and when foreign-key relationships connect corresponding underlying database tables.
For example, suppose you have a simple pair of tables connected by a master-detail relationship. You must first commit inserts and updates of the master table, then commit delete/update/insert of the child table, and finally commit the delete on the master table. Such a process is called the "two sweeps" update procedure.
You can always find at least one correct sequence for a "two-sweeps" update, as long as the DataTables relationships form an acyclic graphone with no closed circuits paths (see Figure 1). This holds true no matter how complex the parent-child relationships are among DataTables.
You encounter hierarchical relationships more often than parent-child relationships in real-world applications. Hierarchical relationships are a kind of acyclic graph, though you still use the two-sweeps procedure. Once you determine the correct order, forward-iterate over the table list performing inserts and updates. Then backward-iterate over the table list performing deletes. Be sure to follow these two steps exactly.
If you choose to code your application's persistence layer explicitly, you look at the database structure to determine the correct DataTables order of your two-sweeps procedure at design time.
Things get more complicated if you want to rely on a generic framework that determines the correct two-sweeps table order (given a generic DataSet structure) at run time. You can build an algorithm for determining the correct update order for a generic acyclic graph when the relationships between database tables and DataSet tables match exactly.
Use this algorithm to determine the correct update order in pseudocode:
find all tables which have no incoming relationships
for each of such tables
label1:
add the table to the update order list
for each child table
mark the relationship between this table and
its parent table as visited
if all incoming relationships of the table
are marked as visited
goto label1:
You can execute this algorithm in a .NET language such as C# with about 90 lines of code (see Listing 3).
Q:
Guarantee Delivery Order
I need to call delegates asynchronously. Is there a way to guarantee delivery order while I do this?
A:
.NET has streamlined asynchronous programming, thanks to the introduction of asynchronous delegates like the BeginInvoke method (see Listing 4). You no longer have to make assumptions about the order in which the method calls will be received. However, this can lead to problems with the delivery order. Debug messages usually appear in the order in which the calls were placed, but sometimes the receiving order is different and one message passes over another. In other words, the delivery order is not guaranteed:
?
"Expected" arrival order: A first,
then B
Method A received call from A with
param 67
Method A received call from B with
param 67
in the following two messages
the call from B arrived before the call from A
Method A received call from B with
param 68
Method A received call from A with
param 68
?
Variable delivery order often causes trouble for particular applications, such as logging mechanisms. Fortunately, you can guarantee correct delivery order without making the client renounce asynchronous programming. The secret is to set up a DelegateSerializer queue.
The client passes the delegate and method parameters to the DelegateSerializer, which stores them in an internal queue and returns immediately. An AutoResetEvent is set to the signaled state when a message is placed in the queue. This wakes up a threat, which pulls messages from the queue, then extracts the delegate and calls it synchronously.
Place asynchronous calls using the Dispatcher queue instead of the BeginInvoke method, so you don't complicate the code:
DelSerializer ds = new
DelSerializer ("test");
MyClass c1 = new MyClass ();
X mydel1 = new X(c1.MethodA);
MyClass c2 = new MyClass ();
X mydel;2 = new X(c2.MethodA );
// Pass the Delegate and its parameters to the
// DelegateSerializer
for (int i =0; i<=100;i++) {
ds.Enqueue (mydel1,new object[] {"A" ,
i.ToString ()});
ds.Enqueue (mydel1,new object[] {"B" ,
i.ToString ()});
}
The full implementation of the DelegateSerializer class is a bit more substantial, of course (see Listing 5).