none
How to propagate WCF impersonation to COM object

    Question

  • I’m working on a project to write a wrapper on a COM component in WCF. COM object needs impersonation in some levels to provide certain functionalities to impersonated user. Basically any impersonation in .Net application doesn't propagate to I know if I want to impersonate inside .Net application. I’ve seen following sentence in How To: Use Impersonation and Delegation in ASP.NET 2.0

    “The impersonation token does not propagate across threads if you use COM interop with components that have incompatible threading models, or if you use unmanaged techniques to create new threads”

    Actually I have a COM component and I want to use this component in my WCF Services. One of classes in COM object has got a property which returns current user. I wrote a console application to test impersonation and see if impersonation does propagate to COM object or not. I have an impersonation class it impersonates to passed username and password.  Following code returns different username for .net and COM object. It means it doesn’t propagate to COM object.

    static void Main(string[] args)
    {
    	DoWork();
    	Console.ReadLine();
    }
    
    private static void DoWork()
    {
    	ImpersonateClass impersonate = new ImpersonateClass();
    	using (impersonate.ImpersonateUser("AnotherUser",
    					"Domain",
    					"passw0rd"))
    	{
    		COMTESTLib.TestClass obj = new COMTESTLib.TestClass();
    		Console.WriteLine(string.Format("COM:{0} .Net:{1}", obj.CurrentUserName,
    			System.Security.Principal.WindowsIdentity.GetCurrent().Name));
    	}
    }
    //Returns:COM:DOMAIN\CurrentUser .Net:DOMAIN\AnotherUser
    
    

    But I used CoInitializeSecurity to initialize security with impersonation and cloaking. CoInitializeSecurity should be called before any marshalling so I called CoInitializeSecurity as first function call. 

    [DllImport("ole32.dll")]
    public static extern int CoInitializeSecurity(IntPtr pVoid, int
    cAuthSvc, IntPtr asAuthSvc, IntPtr pReserved1, RpcAuthnLevel level,
    RpcImpLevel impers, IntPtr pAuthList, int dwCapabilities, IntPtr
    pReserved3);
    
    static void Main(string[] args)
    {
    	int result = CoInitializeSecurity(IntPtr.Zero, -1,
    	IntPtr.Zero, IntPtr.Zero,
    	RpcAuthnLevel.Connect, RpcImpLevel.Impersonate,
    	IntPtr.Zero, Convert.ToInt32(EoAuthnCap.DynamicCloaking), IntPtr.Zero);
    
    	DoWork();
    	Console.ReadLine();
    }
    
    private static void DoWork()
    {
    	ImpersonateClass impersonate = new ImpersonateClass();
    	using (impersonate.ImpersonateUser("AnotherUser",
    					"domain",
    					"passw0rd"))
    	{
    		COMTESTLib.TestClass obj = new COMTESTLib.TestClass();
    		Console.WriteLine(string.Format("COM:{0} .Net:{1}", obj.CurrentUserName,
    			System.Security.Principal.WindowsIdentity.GetCurrent().Name));
    	}
    }
    //Returns:COM:DOMAIN\AnotherUser .Net:DOMAIN\AnotherUser
    
    

    This code returns same username for .Net and COM object. It means it is propagated to COM object.

    Ok, I want to do exact thing on my WCF Service so I created a WCF Service application and Added a service contract called GetUserNames(). I called CoInitializeSecurity same as I did in console application. But it doesn’t work and CoInitializeSecurity returns 80010119 which is RPC_E_TOO_LATE and means at least one marshaling has happened before this function call. 

    [DllImport("ole32.dll")]
    public static extern int CoInitializeSecurity(IntPtr pVoid, int
    cAuthSvc, IntPtr asAuthSvc, IntPtr pReserved1, RpcAuthnLevel level,
    RpcImpLevel impers, IntPtr pAuthList, int dwCapabilities, IntPtr
    pReserved3);
    
    public string GetUserNames()
    {
    	int result = CoInitializeSecurity(IntPtr.Zero, -1,
    	IntPtr.Zero, IntPtr.Zero,
    	RpcAuthnLevel.Connect, RpcImpLevel.Impersonate,
    	IntPtr.Zero, Convert.ToInt32(EoAuthnCap.DynamicCloaking), IntPtr.Zero);
    	//it returns 80010119 which is RPC_E_TOO_LATE
    	 
    	return DoWork();
    }
    
    private string DoWork()
    {
    	string result;
    	ImpersonateClass impersonate = new ImpersonateClass();
    
    	using (impersonate.ImpersonateUser("AnotherUser",
    					"domain",
    					"passw0rd"))
    	{
    		COMTESTLib.TestClass obj = new COMTESTLib.TestClass();
    		result = string.Format("COM:{0} .Net:{1}", obj.CurrentUserName,
    			System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    	}
    	return result;
    }
    
    
    
    //Client
    static void Main(string[] args)
    {
    	ServiceReference.ServiceImpersonateClient client = new ServiceReference.ServiceImpersonateClient();
    
    	string str = client.GetUserNames();
    	client.Close();
    	Console.WriteLine(str);
    	Console.ReadLine();
    }
    // Returns COM:DOMAIN\CurrentUser .Net:DOMAIN\AnotherUser
    

    Somebody suggested using built-in impersonation method for WCF so I changed my service to following code by setting [OperationBehavior(Impersonation = ImpersonationOption.Required)] and some changes in Web.Congif to make it work. 

    public string GetUserNames()
    {
    	return DoWork();
    }
    
    private string DoWork()
    {
    	string result;
    
    	COMTESTLib.TestClass obj = new COMTESTLib.TestClass();
    	result = string.Format("COM:{0} .Net:{1}", obj.CurrentUserName,
    		System.Security.Principal.WindowsIdentity.GetCurrent().Name);
    
    	return result;
    }
    
    

    I passed user credentials from client for impersonation.

    static void Main(string[] args)
    {
    	ServiceReference.ServiceImpersonateClient client = new ServiceReference.ServiceImpersonateClient();
    	client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
    	client.ClientCredentials.Windows.ClientCredential.Domain = "Domain";
    	client.ClientCredentials.Windows.ClientCredential.UserName = "AnotherUser";
    	client.ClientCredentials.Windows.ClientCredential.Password = "Passw0rd";
    
    	string str = client.GetUserNames();
    	Console.WriteLine(str);
    	Console.ReadLine();
    	client.Close();
    }
    // Returns COM:DOMAIN\CurrentUser .Net:DOMAIN\AnotherUser
    

    But it returns different usernames which means impersonation of WCF does not propagate to COM object.

    This Question is How can I propagate impersonation from WCF to COM object?

     

    Friday, September 10, 2010 1:57 AM

Answers

  • Hi Allen

    Self-hosted WCF was great idea!  Actually it works on self-hosted. But my WCF service should be unattended so that I created a managed service for WCF hosting. To make it work I’ve created three projects. One WCF service library which is used in managed service application. And the third one is a console application for testing. I’ve called CoInitializeSecurity on constructor of my service class as following

     

     

    private ServiceHost serviceHost;
     public WCFServiceHost()
     {
      int result = CoInitializeSecurity(IntPtr.Zero, -1,
      IntPtr.Zero, IntPtr.Zero,
      RpcAuthnLevel.Connect, RpcImpLevel.Impersonate,
      IntPtr.Zero, Convert.ToInt32(EoAuthnCap.DynamicCloaking), IntPtr.Zero);
      //EventLog.WriteEntry(result.ToString());
      InitializeComponent();
     }
     
     protected override void OnStart(string[] args)
     {
      if (serviceHost != null)
      {
      serviceHost.Close();
      }
     
      // Create a ServiceHost for the CalculatorService type and 
      // provide the base address.
      serviceHost = new ServiceHost(typeof(ServiceImpersonate));
     
      // Open the ServiceHostBase to create listeners and start 
      // listening for messages.
     
      serviceHost.Open();
     
     }
     
     protected override void OnStop()
     {
      if (serviceHost != null)
      {
      serviceHost.Close();
      serviceHost = null;
      }
     }
    
    

    It doesn’t work with built in WCF impersonation. But I used my impersonation class inside the service class to impersonate to desired user and it works.

     

     public string GetUserNames()
      {
       ImpersonateClass impersonate = new ImpersonateClass();
    
       using (impersonate.ImpersonateUser("AnotherUser",
              "Domain",
               "passw0rd"))
       {
        return DoWork();
       }
      }
     
      private string DoWork()
      {
       string result;
     
     
    	COMTESTLib.TestClass obj = new COMTESTLib.TestClass();
    	result = string.Format("COM:{0} .Net:{1}", obj.CurrentUserName,
    		System.Security.Principal.WindowsIdentity.GetCurrent().Name);
       return result;
      }

    In the console application I simply called my service like following code:

     

      ServiceReference.ServiceImpersonateClient client = new ServiceReference.ServiceImpersonateClient();
      string str = client.GetUserNames();
      Console.WriteLine(str);
      client.Close();
      Console.ReadLine();
    // Returns COM:DOMAIN\ AnotherUser .Net:DOMAIN\AnotherUser
    
    

     

    Actually this is fine for me but I’m not quite sure about differences between hosting WCF in windows managed service compare to hosting in IIS?

     

     

     

    Thursday, September 16, 2010 5:59 AM
  •  

    Actually this is fine for me but I’m not quite sure about differences between hosting WCF in windows managed service compare to hosting in IIS?

     


    When you host WCF in IIS the process is spawned by IIS and before your code running code managed by IIS will be run in the process, which is almost out of your control. CoInitializeSecurity thus might be called before you calling it.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    Thursday, September 16, 2010 6:36 AM
    Moderator

All replies

  • Hi,

    Do you host WCF in IIS or a Console application/Windows Service?


    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    Tuesday, September 14, 2010 6:14 AM
    Moderator
  • Hi,

    The host in IIS. Basically in debug mode the host is WebDev.WebHost40.exe but I tried bublishing it on IIS the result is the same.

     

    Tuesday, September 14, 2010 6:45 AM
  • Hi,

    What happens if you use self-hosted WCF?


    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    Tuesday, September 14, 2010 7:58 AM
    Moderator
  • Hi Allen

    Self-hosted WCF was great idea!  Actually it works on self-hosted. But my WCF service should be unattended so that I created a managed service for WCF hosting. To make it work I’ve created three projects. One WCF service library which is used in managed service application. And the third one is a console application for testing. I’ve called CoInitializeSecurity on constructor of my service class as following

     

     

    private ServiceHost serviceHost;
     public WCFServiceHost()
     {
      int result = CoInitializeSecurity(IntPtr.Zero, -1,
      IntPtr.Zero, IntPtr.Zero,
      RpcAuthnLevel.Connect, RpcImpLevel.Impersonate,
      IntPtr.Zero, Convert.ToInt32(EoAuthnCap.DynamicCloaking), IntPtr.Zero);
      //EventLog.WriteEntry(result.ToString());
      InitializeComponent();
     }
     
     protected override void OnStart(string[] args)
     {
      if (serviceHost != null)
      {
      serviceHost.Close();
      }
     
      // Create a ServiceHost for the CalculatorService type and 
      // provide the base address.
      serviceHost = new ServiceHost(typeof(ServiceImpersonate));
     
      // Open the ServiceHostBase to create listeners and start 
      // listening for messages.
     
      serviceHost.Open();
     
     }
     
     protected override void OnStop()
     {
      if (serviceHost != null)
      {
      serviceHost.Close();
      serviceHost = null;
      }
     }
    
    

    It doesn’t work with built in WCF impersonation. But I used my impersonation class inside the service class to impersonate to desired user and it works.

     

     public string GetUserNames()
      {
       ImpersonateClass impersonate = new ImpersonateClass();
    
       using (impersonate.ImpersonateUser("AnotherUser",
              "Domain",
               "passw0rd"))
       {
        return DoWork();
       }
      }
     
      private string DoWork()
      {
       string result;
     
     
    	COMTESTLib.TestClass obj = new COMTESTLib.TestClass();
    	result = string.Format("COM:{0} .Net:{1}", obj.CurrentUserName,
    		System.Security.Principal.WindowsIdentity.GetCurrent().Name);
       return result;
      }

    In the console application I simply called my service like following code:

     

      ServiceReference.ServiceImpersonateClient client = new ServiceReference.ServiceImpersonateClient();
      string str = client.GetUserNames();
      Console.WriteLine(str);
      client.Close();
      Console.ReadLine();
    // Returns COM:DOMAIN\ AnotherUser .Net:DOMAIN\AnotherUser
    
    

     

    Actually this is fine for me but I’m not quite sure about differences between hosting WCF in windows managed service compare to hosting in IIS?

     

     

     

    Thursday, September 16, 2010 5:59 AM
  •  

    Actually this is fine for me but I’m not quite sure about differences between hosting WCF in windows managed service compare to hosting in IIS?

     


    When you host WCF in IIS the process is spawned by IIS and before your code running code managed by IIS will be run in the process, which is almost out of your control. CoInitializeSecurity thus might be called before you calling it.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Windows Azure Platform China Blog: http://blogs.msdn.com/azchina/default.aspx
    Thursday, September 16, 2010 6:36 AM
    Moderator