Ahmadreza's Notes

On Software Development

Posts Tagged ‘WCF

HOW TO propagate WCF Impersonation to COM objects

I was working on a project to write a wrapper on a COM component in WCF. The COM object needs impersonation in some levels to provide certain functionalities to impersonated user. Basically impersonation in .Net application doesn’t propagate COM calls. 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 wrote a class for impersonation and  it has a method called “ImpersonateUser” that 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</pre>

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</pre>

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.

[OperationBehavior(Impersonation = ImpersonationOption.Required)]
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.

Ok I posted this issue in WCF Forum and I’ve got a simple response but it gave me a good clue. Allen Chen (The moderator) suggested me to use self-hosting for WCF instead of IIS hosting.

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

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.

Advertisements

Written by Ahmadreza Atighechi

September 16, 2010 at 5:19 pm

Posted in Blog

Tagged with , ,

Hosting WCF Service in IIS and Configuration may be needed

This article is an attempt to depict steps may needed to configure IIS for hosting WCF application. An important thing that should be considered  is that all user do not face following situations. First of all I try to create a simple WCF application and then I try to publish this simple WCF simple application.

In order to create a WCF application we just need to know ABC of WCF application which are Address, Binding and Contract. (You can download code from here)

First of all I created a blank solution with Visual Studio 2008 by clicking File > New > Project and selecting Other Project Types > Visual Studio Solutions > Blank Solution

I created solution named AccountSolution. I added new project named Accounting by right clicking on AccountingSolution and selecting Add > New project menu. In the next step I added reference of System.ServiceModel to project references.

I changed name of class1.cs to IAccount.cs and I added new class named AccountService.cs. Here is code for IAccount and AccountService:

IAccount.cs

using System;
using System.Text;
using System.ServiceModel;
 
 
namespace Accounting
{
    [ServiceContract]
    public interface IAccount
    {
        [OperationContract]
        int GetBalance(int accountNumber);
    }
}

 

AccountService.cs

using System;
using System.Text;
using System.ServiceModel;
 
namespace Accounting
{
    public class AccountService : IAccount
    {
        #region IAccount Members
 
        public int GetBalance(int accountNumber)
        {
            Random rnd = new Random(1000000);
            return Convert.ToInt32(rnd.Next());
        }
 
        #endregion
    }
}

 

I added new web site named WCFServiceAccount and added new project reference of Accounting. Since I added project reference to Accounting project, cs under App_Data folder is no longer needed. So let delete two cs file under App_Data.

 

I changed Service.svc source to:

<%@ ServiceHost Language="C#" Debug="true" Service="Accounting.AccountService"%>

Setting Service and end points:

In order to setService and end points I used WCF Configuration option by right-clicking on web.config.

Tip: Sometimes "Edit WCF Configuration" does not appear in right-click menu. Once open "WCF Service Configuration" from tools menu and close it, this cause menu appear next time in right click menu.

I set first service name to Accounting.AccountService by browsing Accounting.dll in bin directory.

And I set first end point contract attribute to Accounting.IAccount and delete second end point.

And at last save the WCF configuration and build solution. Before running web site let set WCFServiceAccount as startup project and set Service.svc as start page. Our web site is ready to be executed. After successful execution let host our website to local IIS. Simply website could be hosted by publishing web site to local IIS. If IIS is installed before visual studio 2008 you may not face with any following errors and simply you can jump next step.

I tried to publish WCFServiceAccount by right clicking on Web site project and selecting "…" then Local IIS but I’ve got following errors. (My windows was Vista Ultimate)

To Access local IIS Web sites, you must install the following IIS component.

  • IIS 6 Metabase and IIS 6 Configuration Compatibility
  • ASP.NET
  • Windows Authentication

Configuring IIS to publish a web site (Specially WCF web site):

To rectify mentioned errors I’ve found a document in Technet for installing IIS6 Metabase and IIS 6 Configuration

To install the IIS 6.0 Management Compatibility Components by using the Windows Vista Control Panel

  1. Click Start, click Control Panel, click Programs and Features, and then click Turn Windows features on or off.
  2. Open Internet Information Services.
  3. Open Web Management Tools.
  4. Open IIS 6.0 Management Compatibility.
  5. Select the check boxes for IIS 6 Metabase and IIS 6 configuration compatibility and IIS 6 Management Console.
  6. Click OK.

Pasted from <http://technet.microsoft.com/en-us/library/bb397374.aspx>

Following image shows how to install "IIS6 Metabase and IIS 6 Configuration ". Surprisingly you can set other option such as Installing ASP.NET and Windows Authentication.

Alternatively, For installing ASP.NET you can use visual studio command prompt and running aspnet_regiis with -i parameter:

aspnet_regiis -i

 

Publishing WCFServiceAccount to local IIS:
  1. Right click on WCFServiceAccount select publish from menu

     
  2. Select local IIS option
  3. Select create new application icon
  4. Type web application (e.g. WCFServiceAccount)

     
  5. Now open a browser to test your application "http://localhost/WCFServiceAccount/Service.svc&quot;
  6. Then select Open button

If you get following error:

Server Error in ‘/WCFServiceAccount’ Application.


Configuration Error

Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.Parser Error Message: Unrecognized configuration section system.serviceModel.

Source Error:

Line 101:
Line 102:
Line 103: Line 104:
Line 105:

Source File: C:\inetpub\wwwroot\WCFServiceAccount\web.config    Line: 103


Version Information: Microsoft .NET Framework Version:2.0.50727.3053; ASP.NET Version:2.0.50727.3053  

OR

Server Error in Application "Default Web Site/WCFServiceAccount"

HTTP Error 404.3 – Not Found

Description: The page you are requesting cannot be served because of the Multipurpose Internet Mail Extensions (MIME) map policy that is configured on the Web server. The page you requested has a file name extension that is not recognized, and is not allowed.

Error Code: 0x80070032

Notification: ExecuteRequestHandler

Module: StaticFileModule

Requested URL: http://localhost:80/WCFServiceAccount/Service.svc

Physical Path: E:\Projects\Learning\Blog\WCFServiceAcount\Service.svc

Logon User: Anonymous

Logon Method: Anonymous

Handler: StaticFile

Most likely causes:

  • It is possible that a handler mapping is missing. By default, the static file handler processes all content.
  • The feature you are trying to use may not be installed.
  • The appropriate MIME map is not enabled for the Web site or application. (Warning: Do not create a MIME map for content that users should not download, such as .ASPX pages or .config files.)

What you can try:

  • In system.webServer/handlers:
  • Ensure that the expected handler for the current page is mapped.
  • Pay careful attention to preconditions (e.g. runtimeVersion, pipelineMode, bitness) and compare them to the settings for your application pool.
  • Pay careful attention to typographical errors in the expected handler line.
  • Please verify that the feature you are trying to use is installed.
  • Verify that the MIME map is enabled or add the MIME map for the Web site using the command-line tool appcmd.exe.
    1. Open a command prompt and change directory to %windir%\system32\inetsrv.
    2. To set a MIME type, use the following syntax: appcmd set config /section:staticContent /+[fileExtension=’string’,mimeType=’string’]
    3. The variable fileExtension string is the file name extension and the variable mimeType string is the file type description.
    4. For example, to add a MIME map for a file which has the extension ".xyz", type the following at the command prompt, and then press Enter:
    5. appcmd set config /section:staticContent /+[fileExtension=’.xyz’,mimeType=’text/plain’]

      Warning: Ensure that this MIME mapping is needed for your Web server before adding it to the list. Configuration files such as .CONFIG or dynamic scripting pages such as .ASP or .ASPX, should not be downloaded directly and should always be processed through a handler. Other files such as database files or those used to store configuration, like .XML or .MDF, are sometimes used to store configuration information. Determine if clients can download these file types before enabling them.
  • Create a tracing rule to track failed requests for this HTTP status code. For more information about creating a tracing rule for failed requests, click here.

More Information…This error occurs when the file extension of the requested URL is for a MIME type that is not configured on the server. You can add a MIME type for the file extension for files that are not dynamic scripting pages, database, or configuration files. Process those file types using a handler. You should not allows direct downloads of dynamic scripting pages, database or configuration files.



Server Version Information: Internet Information Services 7.0.

You should register service model according to following steps:

  1. Run a Visual studio Command prompt

  2. Enter "cd c:\windows\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\" And press enter

  3. Run "servicemodelreg -i"

Written by Ahmadreza Atighechi

May 11, 2009 at 3:36 pm

Posted in Articles

Tagged with ,