Microsoft KB Archive/325791

From BetaArchive Wiki

Article ID: 325791

Article Last Modified on 3/22/2007



APPLIES TO

  • Microsoft Web Services Enhancements for Microsoft .NET 1.1
  • Microsoft Web Services Enhancements for Microsoft .NET 2.0
  • Microsoft COM+ 2.0 Standard Edition



This article was previously published under Q325791

SYMPTOMS

If you impersonate a particular account in your ASP.NET Web Service (.asmx) or in your Web Form (.aspx), and then you call to an Apartment/Single-threaded Component Object Model (COM) component, the process identity (by default, ASPNET account) is used instead of the impersonated account. Therefore, logic errors may occur when you try to access resources from the COM component, or you may receive the following error message:

Access Denied

CAUSE

The thread that executes your ASP.NET request is one of the I/O threads or one of the workerThreads in the ASP.NET worker process (aspnet_wp.exe). These threads are all Multi-Threaded Apartment (MTA) threads. If you programmatically impersonate an account in your .aspx or .asmx code, or if you impersonate by using <identity impersonate="true"> in Web.config or Machine.config, then the impersonation token is held on this MTA thread. If you then make a call into a single-threaded or an apartment-threaded COM component, that component is accessed by an entirely different thread, which is the single thread in its Single-Threaded Apartment (STA). Because this STA thread does not have an impersonation token of its own, and the impersonation token of the MTA thread is not passed to the STA thread, the STA thread then executes under the process identity.

RESOLUTION

To work around this problem for Web Forms or for Web Services, you can do the following:

For Web Forms (.aspx)

Use AspCompat="true" to run your page on an STA thread and to avoid the thread switch when you call the component.

See Q308095 in the "References" section of this article for more information.

For Web Services (.asmx)

AspCompat is not available for Web Services (.asmx) so you must take a different approach. If the impersonated account is static, which means that you specify a userName and a password in the <identity> tag of Web.config or Machine.config, or if you always programmatically impersonate the same account, you can put the STA component in a COM+ Server Application and then set the identity of the application to the impersonated user.

Note When you call an apartment-threaded or single-threaded COM component from a Web Service, it is already a requirement to put the component in some type of COM+ Application.

See Q303375 in the "References" section of this article for more information.

If the impersonated account is dynamic (<identity impersonate="true">+ IIS Integrated or Basic authentication), setting the identity in a COM+ server application will not suffice. In that case, if you control the code for the STA component then you can call the CoImpersonateClient function at the beginning of each method in the component that requires the impersonated identity. If you do not control the code for the STA component, or if the code for the component is written by a third party, then you can introduce a wrapper STA component that first calls CoImpersonateClient and then calls in the third-party component. There are two approaches you can use:

  • If you must use early binding from your clients, you can create a separate wrapper component for each third-party component. Each wrapper component needs separate wrapper methods for each method in its third-party component.
  • If you can use late binding from your clients, you can just create one generic wrapper component. This wrapper component may create any third-party component by using ProgID, and it would need only one generic Invoke method to call into the third-party component.

See the CallByName function in the "References" section of this article for more information.

The following example shows how you can:

  • Create a new STA thread
  • Pass data to the new thread
  • Cause the new thread to run under the same identity that the Web service is impersonating
  • Wait for the new thread to finish calling the STA component
  • Extract the results from the new thread and return the results to the client

Note The intent of this sample is only to show how to use your own STA thread to impersonate and directly access the COM component. In a production environment, it is best to manage a pool of STA threads instead of creating and destroying a single thread on each request.

If you use the example that follows, your STA thread is destroyed immediately after you call the COM component, and then you cannot reference any other COM objects that were created in this STA. Therefore, if your COM object returns a reference to a second COM object that is created in the same STA, you receive the following InvalidComObjectException:

COM object that has been separated from its underlying RCW cannot be used

.

To make this work you must keep the STA thread alive and then pump messages to invoke the second COM object, which is beyond the scope of this example.

Create a Text File with Limited Permissions

  1. Create a text file that is named C:\permissions.txt. Enter some text into the file.
  2. Right-click Permissions.txt in Microsoft Windows Explorer, and then click Properties.
  3. On the Security tab, remove all users, and then give full control to the user who is currently logged on.

Create an STA COM Component to Access the Text File

  1. Create a new Microsoft Visual Basic 6 ActiveX DLL project named FSOWrapper.
  2. Add a new Class Module that is named CMyFileSystemObject, and then paste in the following code:

       
    Option Explicit
    
    Dim m_fso As FileSystemObject
    
    Private Sub Class_Initialize()
        Set m_fso = New FileSystemObject
    End Sub
    
    Public Function OpenTextFileUsingDefaults(ByVal FileName As String) As CMyTextStream
        Set OpenTextFileUsingDefaults = OpenTextFile(FileName)
    End Function
    
    Public Function OpenTextFile( _
        ByVal FileName As String, _
        Optional ByVal IOMode As IOMode = ForReading, _
        Optional ByVal Create As Boolean = False, _
        Optional ByVal Format As Tristate = TristateFalse) As CMyTextStream
        
        Dim oStream As TextStream
        Set oStream = m_fso.OpenTextFile(FileName, IOMode, Create, Format)
        
        Set OpenTextFile = New CMyTextStream
        OpenTextFile.Init oStream
    End Function
    
                        
  3. Add a new Class Module that is named CMyTextStream, and then paste in the following code:

    Option Explicit
    
    Dim m_oStream As TextStream
    
    Friend Sub Init(oStream As TextStream)
        Set m_oStream = oStream
    End Sub
    
    Public Function ReadAll() As String
        ReadAll = m_oStream.ReadAll
    End Function
                        
  4. Compile FSOWrapper.dll.

Create an ASP.NET WebService to Call the STA COM Component

  1. Create a new C# ASP.NET WebService project in Microsoft Visual Studio .NET.
  2. In the Location field, type http://localhost/STAWebService
  3. In the Internet Information Services snap-in for MMC, visit the STAWebService virtual directory, and then click Properties. Verify that the Integrated Windows authentication is the only Authentication Method that is selected.
  4. Add the following under the <system.web> node of the Web.config file: <identity impersonate="true"/>
  5. Paste the following code into the Service1.asmx.cs file:
using System;

using System.Web.Services;

 

using FSOWrapper;

using System.Threading;

using System.Security.Principal;

using System.Runtime.InteropServices;

using System.Text;

 

namespace STAWebService

{

       [WebService()]

       public class Service1 : WebService

       {             

              /// Create an STA thread under the current impersonated identity

              /// so that it can access the STA DLL directly without losing the impersonation.

              [WebMethod]

              public string TestOpenFileFromSTA(string sFile)

              {

                     StringBuilder returnString = new StringBuilder();

                     try

                     {

                           // Start the output...

                           StartOutput(returnString);

 

                           // Create/init our MySTAThread object.

                           MySTAThread myThread = new MySTAThread(sFile);

 

                           // Start up this new STA thread.

                           myThread.Start();

 

                           // Wait for the new thread to finish.

                           bool bWaitRet = myThread.Event.WaitOne(1000,false);

 

                           // Finish the output...

                           FinishOutput(returnString, bWaitRet, myThread);

                     }

                     catch (Exception e)

                     {

                           returnString.Append("<Exception_in_original_thread message = \"" + e.Message + "\"/>");

                     }

 

                     return returnString.ToString();

              }

 

              private void StartOutput(StringBuilder returnString)

              {

                     returnString.Append("<Results_from_original_thread>");

                     returnString.Append("<OriginalIdentity value = \"" + WindowsIdentity.GetCurrent().Name + "\"/>");

              }

              private void FinishOutput(StringBuilder returnString, bool bWaitRet, MySTAThread myThread)

              {

                     returnString.Append("<FinalIdentity value = \"" + WindowsIdentity.GetCurrent().Name + "\"/>");

                     returnString.Append("<Done_within_timeout value = \"" + bWaitRet + "\"/>");

                     returnString.Append("</Results_from_original_thread>");                                                                                  

                     returnString.Append("<Results_from_myThread>");

                     returnString.Append("<Success value = \"" + myThread.Success + "\"/>");

                     returnString.Append("<ImpersonatedIdentity value = \"" + myThread.ImpersonatedIdentity + "\"/>");

                     returnString.Append("<FileContents value = \"" + myThread.FileContents + "\"/>");

                     returnString.Append("<Exception value = \"" + myThread.Exception + "\"/>");

                     returnString.Append("</Results_from_myThread>");

              }

       }

       public class MySTAThread

       {

              [DllImport("advapi32")]

              public static extern bool RevertToSelf();

 

              // You can use public members (or properties) for output...

              public bool Success;

              public string FileContents;

              public string Exception;          

              public string ImpersonatedIdentity;

 

              // Public event so caller can wait.

              public AutoResetEvent Event;

 

              // Privates...

              Thread theThread;

              WindowsIdentity impersonatedIdentity;

              string fileName;

 

              public MySTAThread(string fileName)

              {

                     // Init values;

                     Success = false;

                     Exception = "There was no error!";

                     this.fileName = fileName;

 

                     // Create Thread and Event. 

                     Event = new AutoResetEvent(false);                                   

                     theThread = new Thread(new ThreadStart(MySTAThreadStart));

 

                     // Intialize to an STA so that there will be no thread switch to the STA COM object.

                     theThread.ApartmentState = ApartmentState.STA;

              }

 

              public void Start()

              {

                     // Hang on to the current (impersonated) Identity.

                     impersonatedIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();

 

                     // This thread is currently impersonating so any thread you start 

                     // will not have permission to impersonate. You must drop the 

                     // current impersonation token so that your new thread can impersonate.

                     RevertToSelf();

 

                     // Start up the new thread.

                     theThread.Start();

 

                     // Return to the original (impersonated) identity.

                     impersonatedIdentity.Impersonate();

              }

 

              void MySTAThreadStart()

              {

                     try

                     {      

                           // Impersonate using the Token property.

                           WindowsImpersonationContext ctx = impersonatedIdentity.Impersonate();

 

                           // Store current name of the user for verification.

                           ImpersonatedIdentity = (WindowsIdentity.GetCurrent().Name);

                     

                           // Access some STA COM object that requires impersonation.

                           CMyFileSystemObject oMyFSO = new CMyFileSystemObjectClass();

                           CMyTextStream oMyTxtStream = oMyFSO.OpenTextFileUsingDefaults(fileName);

                           FileContents = oMyTxtStream.ReadAll();

                           Success = true;

                     }

                     catch(Exception e)

                     {

                           Exception = e.Message ;

                           Success = false;

                     }

                     finally

                     {

                           // Drop the impersonation token now that you are finished with it.

                           RevertToSelf();

 

                           // Set the Event property so that the creator can stop waiting on this thread.

                           Event.Set();

                     }

              }

       }

}

                

Run the WebService from Internet Explorer

  1. Click Debug | Start Without Debugging (CTRL+F5) menu to start WebService in Internet Explorer.
  2. Click the OpenFileFromSTA link.
  3. Type C:\permissions.txt for the sFile field, and then click Invoke.


On the new page, you may see code similar to the following:

  <?xml version="1.0" encoding="utf-8" ?>

<string xmlns="http://tempuri.org/">

       <Results_from_original_thread>

              <OriginalIdentity value="MYDOMAIN\myuser" />

              <FinalIdentity value="MYDOMAIN\myuser" />

              <Done_within_timeout value="True" />

       </Results_from_original_thread>

       <Results_from_myThread>

              <Success value="True" />

              <ImpersonatedIdentity value="MYDOMAIN\myuser" />

              <FileContents value="this is some stuff in the file." />

              <Exception value="There was no error!" />

       </Results_from_myThread>

</string>

                

STATUS

This behavior is by design.

REFERENCES

For more information, click the following article numbers to view the articles in the Microsoft Knowledge Base:

308095 Creating STA components in the constructor in ASP.NET ASPCOMPAT mode negatively affects performance


303375 XML Web services and Apartment objects


306158 How to implement impersonation in an ASP.NET application


306590 ASP.NET security overview


For additional information, visit the MSDN Web site:

Keywords: kbclient kbdevsecurity kbprb kbsecurity KB325791