PRB: Problem with the COM+ Security When Impersonating the User (305731)



The information in this article applies to:

  • Microsoft COM+ 1.0

This article was previously published under Q305731

SYMPTOMS

When a Component Object Model (COM) component impersonates a specific user and calls other COM components, you expect the called components to run under the identity of the impersonated user. You may not see the same behavior when the components are deployed in COM+ server applications.

CAUSE

If the components are deployed as separate server packages, they run under the identity that is determined by the identity of the COM+ application. They run under the caller's identity only when this is specified by using the CoImpersonateClient function.

This behavior is by design. Note also that this is standard COM behavior and not unique to COM+.

MORE INFORMATION

Example

Name the first COM component Component1 and assume that it impersonates a specific user. Also assume that Component1 calls another COM component called "Server".

Case 1: Client and Server Are In-Process Components

Both Component1 and Component2 are legacy in-process servers, which means that they would be running under the calling thread's identity. Component1 calls the LogonUser and ImpersonateLoggedOnUser functions, which make the current thread's identity that of the specified user, hence Component2 runs as the same user.

Case 2: Client and Server Are Deployed in Separate COM+ Applications

When Component1 and Component2 are in separate COM+ applications, they become out-of-process servers, each running in its own DLLHost.exe process. Although Component1 changes the current thread identity to that of "userA", Component2 still runs as the user specified in its Identity tab (RunAs key), because the call will be delivered across the process boundary. However, Component2 could call CoImpersonateClient or the CoImpersonateClient function and could have impersonated its caller (userA) for the duration of the method call or until the CoRevertToSelf function is called.

Steps to Reproduce the Problem

  1. Create Component2.dll, and then add the following code to its class:
    ...
    BOOLEAN GetAppUserName(BSTR *pbstAppUserName)
    {
    	LPTSTR		ptstrUserName = NULL;
    	DWORD		dwLength = (1024 + 1) * sizeof(TCHAR);
    	BOOLEAN		bSuccess = false;
    	CComBSTR	cbstUserName;
    	
    	if ((ptstrUserName = (LPTSTR)LocalAlloc(LPTR, dwLength)))
    	{
                    //Retrieve the Caller's identity
    		if (GetUserNameEx(NameSamCompatible,ptstrUserName,&dwLength))
    			cbstUserName = ptstrUserName;
    
    		LocalFree(ptstrUserName);
    		*pbstAppUserName = cbstUserName.Copy();
    		bSuccess = true;
    	}
    
    	return bSuccess;
    }
    
    STDMETHODIMP CComponent2::Test(void)
    {
    	USES_CONVERSION;
    	CComBSTR cbstUserName;
    
    	GetAppUserName(&cbstUserName);
    
    	MessageBox(NULL, OLE2T(cbstUserName), LPCSTR(L"Server"), MB_OK);
    
    	return S_OK;
    }
    
    					
  2. Follow these steps:
    1. Add the following include:
      #include "Security.h"
      						
    2. Add Secur32.lib to the included libraries.
    3. Add the following precompiler directive:
      SECURITY_WIN32
      						
  3. Compile the code, and then build Component1.dll.
  4. Build Component1.dll as a COM component. Add the following code to the component's class:
    ...
    // CComp1
    BOOLEAN GetAppUserName(BSTR *pbstAppUserName)
    {
    	LPTSTR		ptstrUserName = NULL;
    	DWORD		dwLength = (1024 + 1) * sizeof(TCHAR);
    	BOOLEAN		bSuccess = false;
    	CComBSTR	cbstUserName;
    	
    	if ((ptstrUserName = (LPTSTR)LocalAlloc(LPTR, dwLength)))
    	{
    	   if (GetUserNameEx(NameSamCompatible,ptstrUserName,&dwLength))
    			cbstUserName = ptstrUserName;
    
    	    LocalFree(ptstrUserName);
    	    *pbstAppUserName = cbstUserName.Copy();
    	    bSuccess = true;
    	}
    
    	return bSuccess;
    }
    
    STDMETHODIMP CComp1::Test(void)
    {
    	CComPtr<IComponent2>	spICompServ;
    	HRESULT		hr;
    	CComBSTR	cbstUserName;
    	CComBSTR	cbstUserId;
    	CComBSTR	cbstPassword;
    	CComBSTR	cbstDomain;
    	HANDLE		hToken;
    
    	USES_CONVERSION;
    
    	cbstUserId += L"account";
    	cbstPassword += L"password12";
    	cbstDomain += L"machinename";
    
    	if (!LogonUser(OLE2T(cbstUserId), OLE2T(cbstDomain), OLE2T(cbstPassword),LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,&hToken))
    	{
    		return E_FAIL;
    	}
    		
    	if (!ImpersonateLoggedOnUser(hToken))
    	{
    		return E_FAIL;
    	}
    
    	GetAppUserName(&cbstUserName);
    
    	MessageBox(NULL, OLE2T(cbstUserName), "Component 1", MB_OK);
    
    	//Create "Server"
            hr = spICompServ.CoCreateInstance(CLSID_CComponent2);
            //Call Server's method  
            hr = spICompServ->Test();                  
    	
    	RevertToSelf();
    	CloseHandle(hToken);
    
    	return hr;
    }
    ...
    					
  5. Again, follow these steps:
    1. Add the following include:
      #include "Security.h"
      						
    2. Add Secur32.lib to the included libraries.
    3. Add the following precompiler directive:
      SECURITY_WIN32
      						
  6. Add Component2_i.c and Component2.h includes.
  7. Create a client application that calls these COM components. You can save the following in a text file and rename the extension to .vbs:
    Dim o 
        
    Set o = CreateObject("Component1.Comp1")
        
    o.Test
    End Sub
    					
If Component1 and Component2 are registered as in-process components, the components display message boxes with "corpdomain\userA". If Component1 and Component2 are registered as separate COM+ applications, Component1 displays "corpdomain\userA", and Component2 displays the username of the identity of the COM+ application in which it resides.

Modification Type:MajorLast Reviewed:2/13/2002
Keywords:kbprb KB305731