A
Anil Krishnamurthy
We have an ASP.NET application that uses COM objects through Interop. The
web application requires access to network and database resources and hence,
needs to impersonate a domain account. The problem is that even when it is
configured to run under a certain identity through Web.config, the
impersonation is not carried through to COM library. Consequently, the code
in COM object runs under a local account and any code that needs to access
network will not work correctly.
ASP.NET
{Web app} -------------Interop --------------->{COM Library}
(Domain\NetworkUser)
(LocalHost\IUSR_MachineName)
We tried to solve the problem by impersonating the caller in COM library.
Instead of using CoImpersonateClient(), which is required for every method,
we tried using a different approach so that impersonation is in effect
beyond function call. It was implemented as follows:
HRESULT CClientImpersonator::Impersonate()
{
BOOL bOk = FALSE;
HRESULT hr = E_FAIL;
DWORD dwErr = 0;
// try to impersonate the client
hr = ::CoImpersonateClient();
if ( SUCCEEDED( hr ) )
{
HANDLE hToken = NULL;
HANDLE hDupToken = NULL;
// get the thread's impersonation token
bOk = ::OpenThreadToken( ::GetCurrentThread(), TOKEN_ALL_ACCESS,
TRUE, &hToken );
if ( TRUE == bOk )
{
// dup it
bOk = :uplicateTokenEx( hToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenImpersonation, &hDupToken );
if ( TRUE == bOk )
{
// switch back the identity
hr = ::CoRevertToSelf();
if ( SUCCEEDED(hr) )
{
// now impersonate the same identity so it 'sticks'
beyond function call level
bOk = ::ImpersonateLoggedOnUser( hDupToken );
if ( FALSE == bOk )
{
hr = HRESULT_FROM_WIN32( ::GetLastError() );
ATLTRACE( "ImpersonateLoggedOnUser() failed.
GetLastError() returned %8x.\n", hr );
}
}
else
{
ATLTRACE( "CoRevertToSelf() failed. Error code %8x.\n",
hr );
}
}
else
{
hr = HRESULT_FROM_WIN32( ::GetLastError() );
ATLTRACE( "DuplicateTokenEx() failed. GetLastError()
returned %8x.\n", hr );
}
}
else
{
hr = HRESULT_FROM_WIN32( ::GetLastError() );
ATLTRACE( "OpenThreadToken() failed. GetLastError() returned
%8x.\n", hr );
}
::CloseHandle( hDupToken );
::CloseHandle( hToken );
}
else
{
if ( hr != RPC_E_CALL_COMPLETE )
ATLTRACE( "CoImpersonateClient() failed. Error code: %8x.\n",
hr );
else
hr = S_FALSE; // ignore the failure when the context is not
available
}
return hr;
}
Unfortunately, the solution does not seem to work on all machines. In cases
where it does not work, the behavior is as follows: Instantiation of COM
object succeeds but attempt to invoke any method or access any property on
the same object fails. The error is reported as
"System.InvalidCastException. QueryInterface for interface <Interface name>
failed"
Any ideas?
Thanks
web application requires access to network and database resources and hence,
needs to impersonate a domain account. The problem is that even when it is
configured to run under a certain identity through Web.config, the
impersonation is not carried through to COM library. Consequently, the code
in COM object runs under a local account and any code that needs to access
network will not work correctly.
ASP.NET
{Web app} -------------Interop --------------->{COM Library}
(Domain\NetworkUser)
(LocalHost\IUSR_MachineName)
We tried to solve the problem by impersonating the caller in COM library.
Instead of using CoImpersonateClient(), which is required for every method,
we tried using a different approach so that impersonation is in effect
beyond function call. It was implemented as follows:
HRESULT CClientImpersonator::Impersonate()
{
BOOL bOk = FALSE;
HRESULT hr = E_FAIL;
DWORD dwErr = 0;
// try to impersonate the client
hr = ::CoImpersonateClient();
if ( SUCCEEDED( hr ) )
{
HANDLE hToken = NULL;
HANDLE hDupToken = NULL;
// get the thread's impersonation token
bOk = ::OpenThreadToken( ::GetCurrentThread(), TOKEN_ALL_ACCESS,
TRUE, &hToken );
if ( TRUE == bOk )
{
// dup it
bOk = :uplicateTokenEx( hToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenImpersonation, &hDupToken );
if ( TRUE == bOk )
{
// switch back the identity
hr = ::CoRevertToSelf();
if ( SUCCEEDED(hr) )
{
// now impersonate the same identity so it 'sticks'
beyond function call level
bOk = ::ImpersonateLoggedOnUser( hDupToken );
if ( FALSE == bOk )
{
hr = HRESULT_FROM_WIN32( ::GetLastError() );
ATLTRACE( "ImpersonateLoggedOnUser() failed.
GetLastError() returned %8x.\n", hr );
}
}
else
{
ATLTRACE( "CoRevertToSelf() failed. Error code %8x.\n",
hr );
}
}
else
{
hr = HRESULT_FROM_WIN32( ::GetLastError() );
ATLTRACE( "DuplicateTokenEx() failed. GetLastError()
returned %8x.\n", hr );
}
}
else
{
hr = HRESULT_FROM_WIN32( ::GetLastError() );
ATLTRACE( "OpenThreadToken() failed. GetLastError() returned
%8x.\n", hr );
}
::CloseHandle( hDupToken );
::CloseHandle( hToken );
}
else
{
if ( hr != RPC_E_CALL_COMPLETE )
ATLTRACE( "CoImpersonateClient() failed. Error code: %8x.\n",
hr );
else
hr = S_FALSE; // ignore the failure when the context is not
available
}
return hr;
}
Unfortunately, the solution does not seem to work on all machines. In cases
where it does not work, the behavior is as follows: Instantiation of COM
object succeeds but attempt to invoke any method or access any property on
the same object fails. The error is reported as
"System.InvalidCastException. QueryInterface for interface <Interface name>
failed"
Any ideas?
Thanks