Page #87 (Declarative Security)


Server-Side Security Programming

When the client makes a method call to the server, COM creates a call context and makes it available to the server. Do not confuse the call context with the object context (actually the object s context object) that was covered in Chapter 5. Note the following differences:

  • The context object represents the context of the server object. Its lifetime is tied to the lifetime of the server object. The call context represents the context of the method call. It is valid only for the duration of the method call.

  • The context object is obtained using the CoGetObjectContext API. The call context is obtained using the CoGetCallContext API.

There are only two interfaces that deal with security on the server side, IServerSecurity and ISecurityCallContext.

IServerSecurity is used by a component to identify and/or impersonate a client. The following code shows the IDL definition of this interface:

 interface IServerSecurity : IUnknown  {   HRESULT QueryBlanket (   [out] DWORD *pAuthnSvc,    [out] DWORD *pAuthzSvc,    [out] OLECHAR **pServerPrincName,    [out] DWORD *pAuthnLevel,    [out] DWORD *pImpLevel,    [out] void **pPrivs,    [in, out] DWORD *pCapabilities );    HRESULT ImpersonateClient();    HRESULT RevertToSelf();    BOOL IsImpersonating();  } 

The stub implements the IServerSecurity interface, so there is no reason to implement this interface unless you are using custom marshaling.

To obtain a pointer to the IServerSecurity interface, the server can call the CoGetCallContext API within a method call, as shown below:

 STDMETHODIMP CMyServer::SomeMethod()  {   CComPtr<IServerSecurity> spSec;    HRESULT hr =  ::CoGetCallContext(__uuidof(IServerSecurity),      (void**) &spSec);       } 


CoGetCallContext can be called only from the thread invoked by a client (as a result of some method call).

Also note that if you spawn a secondary thread in your server code, there is no call context available to the thread. A call to CoGetCallContext will fail.

The IServerSecurity::QueryBlanket method is used to obtain the security blanket of the call context. The following code fragment shows how to dump various parameters of the security blanket. This code can be found on the CD. For brevity, I have removed the error checking logic from the code:

 void DisplaySecurityBlanket()  {   CComPtr<IServerSecurity> spSec;    ::CoGetCallContext(__uuidof(IServerSecurity), (void**) &spSec);    DWORD dwAuthnSvc, dwAuthzSvc, dwAuthnLevel,      dwImpLevel, dwCapabilities;    OLECHAR* pPrincipalName = NULL;    RPC_AUTHZ_HANDLE hPrivs;    hr = spSec->QueryBlanket(     &dwAuthnSvc,      &dwAuthzSvc,      &pPrincipalName,      &dwAuthnLevel,      &dwImpLevel,      &hPrivs,      &dwCapabilities);    _bstr_t bsDisplay = _T("Principal name: ");    if (NULL == pPrincipalName) {     bsDisplay += "(Unknown)";    }else {     bsDisplay += pPrincipalName;      ::CoTaskMemFree(pPrincipalName); pPrincipalName = NULL;    }    bsDisplay += "\nPrivilege name: ";    if (NULL == hPrivs) {     bsDisplay += "(Unknown)";    }else {     bsDisplay += reinterpret_cast<LPCWSTR>(hPrivs);    }    TCHAR buf[256];    _stprintf(buf, _T("\ndwAuthnSvc=%d, dwAuthzSvc=%d,      dwAuthnLevel=%d, dwImpLevel=%d, dwCapabilities=%d"),      dwAuthnSvc, dwAuthzSvc, dwAuthnLevel,      dwImpLevel, dwCapabilities);    bsDisplay += buf;    ::MessageBox(NULL, bsDisplay, _T("Security blanket"), MB_OK);  } 

The following points should be noted about the QueryBlanket method:

  1. The principal name returned via pPrincipalName is the account under which the server application is running. The client s principal name can be obtained via the pPrivs parameter but only if the authentication service being used is either NTLM or Kerberos.

  2. The current implementation of COM (under Windows 2000) does not return the correct impersonation level (via dwImpLevel parameter).

To impersonate a client, ImpersonateClient can be called. To restore the thread back to its original security context, RevertToSelf can be called. If RevertToSelf is not called explicitly after calling ImpersonateClient, COM automatically calls RevertToSelf when the method returns.

The server can also check if it is currently under impersonation by calling IsImpersonating API.

Finally, to simplify dealing with the IServerSecurity interface, COM provides several helper functions, as shown in Table 7.5.

Table 7.5. IServerSecurity Helper Functions


Helper Function









Interface IServerSecurity is available for components based on classic COM as well as COM+ configured components. COM+ configured components can also avail another interface, ISecurityCallContext, by calling CoGetCallContext, as shown in the following code snippet:

 STDMETHODIMP CMyServer::SomeMethod()  {   CComPtr<ISecurityCallContext> spSec;    HRESULT hr =      ::CoGetCallContext(_uuidof(ISecurityCallContext),        (void**) &spSec);       } 


Interface ISecurityCallContext is available only for COM+ configured components. In addition, the application should be marked to check access roles at the process and the component levels.

The following code shows the IDL definition for ISecurityCallContext. [4]

[4] The current SDK does not provide the IDL file for COM+ services interfaces. The header file <comsvcs.h> defines this interface. I worked my way back to construct the interface definition.

 ISecurityCallContext : IDispatch  {   [propget] HRESULT Count([out, retval] long *plCount);    [propget] HRESULT Item([in] BSTR name,      [out, retval] VARIANT *pItem);    [propget] HRESULT NewEnum([out, retval] IUnknown **ppEnum);    HRESULT IsCallerInRole([in] BSTR bstrRole,      [out, retval] VARIANT_BOOL *pfInRole);    HRESULT IsSecurityEnabled(     [out, retval] VARIANT_BOOL *pfIsEnabled);    HRESULT IsUserInRole([in] VARIANT *pUser, [in] BSTR bstrRole,      [out, retval] VARIANT_BOOL *pfInRole);  }; 

To understand how this interface can be used, let s develop a component that manages employee salary. The component implements the following interface:

 interface IEmployeeSalary : IDispatch  {   HRESULT GetSalary([in] BSTR bsEmployeeName,      [out, retval] long *pVal);    HRESULT UpdateSalary([in] BSTR bsEmployeeName,      [in] long newVal);  }; 

Let s define a COM+ server application and add this component to the application. Let s also define two roles, Managers and Employees. The Managers role can access and update the salary for an employee. The Employees role can only access the salary. Logically, it makes sense to declaratively associate both roles with the GetSalary method and only the Managers role with the UpdateSalary method.

This declarative role association has just one problem: any employee can access any other employee s salary information. This is clearly not acceptable. However, declarative programming does have its limitations. You need to programmatically add the logic to ensure that a caller can view only its salary information and nobody else s. The following code snippet demonstrates how IServerSecurityContext can be used to validate the current caller s account identification against the bsEmployeeName parameter and grant access to the salary information:

 STDMETHODIMP CEmployeeSalary::GetSalary(BSTR bsEmployeeName, long *pVal)  {   CComPtr<ISecurityCallContext> spSec;    ::CoGetCallContext(__uuidof(ISecurityCallContext),      (void**) &spSec);    VARIANT_BOOL bFlag;    hr = spSec->IsCallerInRole(CComBSTR("Managers"), &bFlag);    if (VARIANT_TRUE == bFlag) { // managers always have access      *pVal = 100000;      return S_OK;    }              // the caller must be in "Employees" role    // (as set declaratively on the method).    // Get the original caller account name    _bstr_t bsCaller = GetOriginalCaller(spSec);    if (0 != wcscmp(bsCaller, bsEmployeeName)) {     // A different employee      return E_ACCESSDENIED;    }    // validated the account    *pVal = 80000;    return S_OK;  } 

COM+ classifies all the information that is available on the security call context into different collections of properties. To deal with these collections, COM+ defines three types of collection objects SecurityIdentity, SecurityCallers, and SecurityCallContext. Recall from Chapter 2 that a collection object typically supports two methods, Count and Item.

Collection SecurityIdentity contains the identity of a single caller. It is represented by interface ISecurityIdentityColl. Its properties are shown in Table 7.6.

Table 7.6. SecurityIdentity Properties




The security identifier of the caller.


The account name of the caller.


The authentication service used (NTLM, Kerberos, etc).


The authentication level used.


The impersonation level used.

When a client calls a method on the server object, the server object may in turn call a method on another server, which in turn may call another object, ad infinitum. COM+ maintains the list of callers in this chain of calls as part of a SecurityCallers collection. SecurityCallers is essentially a collection of SecurityIdentity objects. It is represented by the ISecurityCallersColl interface.

Collection SecurityCallContext is the one that we have already dealt with earlier. You just didn t know it. It is represented by the ISecurityCallContext interface (now it all becomes clear), and is the doorway to get to other collection objects. The properties supported by this collection are shown in Table 7.7.

Table 7.7. SecurityCallContext Properties





Number of callers in the chain of calls.



Identity of each caller in the chain of calls.



The caller who is the immediate parent.



The caller who originated the chain of calls.



The least secure authentication level of all callers in the chain.


Given the previous collection information, the function GetOriginalCaller referred to in the previous code snippet can be written as follows:

 _bstr_t GetOriginalCaller(ISecurityCallContext* pSec)  {   CComVariant vColl;    pSec->get_Item(CComBSTR("OriginalCaller"), &vColl);    CComPtr<IDispatch> spDisp = V_DISPATCH(&vColl);    CComPtr<ISecurityIdentityColl> spIdentity;    spDisp->QueryInterface(&spIdentity);    CComVariant vAccountName;    spIdentity->get_Item(CComBSTR("AccountName"), &vAccountName);    return V_BSTR(&vAccountName);  } 

Note the heavy dose of VARIANT and IDispatch type variables in the code. This is because all of the collection interfaces dealing with the security call context are geared for automation-compatible languages.


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
Year: 2000
Pages: 129 © 2008-2017.
If you may any questions please contact us: