Vision

Id, Ego and Superego

December 2005

Introduction

We probably should have called the title of this article ‘SharePoint and impersonation’ or some other rather predictable title like that, because that’s the topic of this article. But there’s no fun in that, so instead we decided to call it ‘Id, Ego and Superego’, concepts which are borrowed from the famous psychologist Sigmund Freud and applied very loosely to SharePoint.

Freud used the terms Id, Ego and Superego to describe the human character. The ‘Id’ (pronounced as ‘it’) describes the lower, more animalistic parts of our character. The ‘Ego’ part of the human character describes the way most of us act in everyday life, the Ego part is a compromise between needs, lust, morals and realism. Finally, the ‘Superego’ represents higher values, ethics and morale. Maybe you could say that the Superego is what most of us would like to be like.

Sometimes when creating web parts you might want to do things which ordinary users are not allowed to, for instance, when you want to use the SharePoint administration object model to display information about SharePoint. If you’re feeling lucky you could solve the problem by making everybody administrator but chances are you won’t be nominated to win the Microsoft Trustworthy computing award this year – again.

Another solution would be to change the identity to an account with more privileges during the web part life cycle, do the stuff which demand additional privileges, and change the identity back again to the original context. In this article we will show you three different approaches to impersonation and we’ll use the concepts of Id, Ego and Superego to give those approaches a name.

SharePoint OM code sample requiring administrator privileges

To be able to demonstrate that the impersonation code actually works you’ll need to create an account with Reader rights for SharePoint. We use a small code sample which uses the SharePoint object model to display the URL and server id of the current virtual server. You need to be an administrator to be able to do this. If you create a web part you could override the RenderWebPart method like this:

protected override void RenderWebPart(HtmlTextWriter output)
{
 string strValue = String.Empty;

 try
 {
   SPSite objSite = SPControl.GetContextSite(Context);
   SPGlobalAdmin objAdmin = new SPGlobalAdmin();
   SPVirtualServer objServer = objAdmin.OpenVirtualServer(new Uri(objSite.Url));
   objServer.CatchAccessDeniedException = false;
   strValue += "url:" + objServer.Url + "virtual server id:" + objServer.VirtualServerId;
 }
 catch (Exception err)
 {
    strValue = "Error in wp: " + err.Message;
 }
 output.Write(strValue);
}

The code itself is unimportant, the only thing that is important is that it requires administrator privileges. This makes it an excellent test to see if the impersonation works. By the way, the CatchAccessDeniedException property of the SPSite object is used to specify that access denied errors aren’t handled which prevents authentication dialog boxes from popping up.

If you open a browser by rightclicking it, choosing the ‘Run as’ option and log in with a previously created reader account you’ll see that access will be denied (and if you didn’t set the CatchAccessDeniedException to false you will have plenty of opportunity to notice this).

Policy files

If you want to be able to execute all code samples it’s easiest if you set the trust level in the SharePoint web.config file to ‘Full’, like so:

<trust level="Full" originUrl="" />

We like to work with our own custom policy files. If you’re using custom policy files as well and if you want to be able to execute the ‘Id’ and ‘Ego’ approaches make sure the following permissions are present in your policy file:

<IPermission
class="AspNetHostingPermission"
version="1"
Level="Minimal" />
<IPermission class="EnvironmentPermission"
version="1"
Unrestricted="true"/>
<IPermission
class="SecurityPermission"
version="1"
Flags="Execution,UnmanagedCode,ControlPrincipal,ControlAppDomain,
ControlEvidence" />
<IPermission class="WebPartPermission"
version="1"
Connections="True" />
<IPermission class="SharePointPermission"
version="1"
ObjectModel="True"
UnsafeSaveOnGet="True" />

As for the Superego approach, as it turned out, to be able to execute this scenario we found we needed so much permissions that we chose the easy route and used full trust instead.

Ego

In this approach we’ll use a Win32 API call to the LogonUser function to impersonate another user account. The first thing you need to do is import two dll’s, advapi.dll and kernel32.dll. To do this add the following code to your web part class:

[DllImport("advapi32.dll", SetLastError=true)]
static extern bool LogonUser(
   string principal,
   string authority,
   string password,
   LogonTypes logonType,
   LogonProviders logonProvider,
   out IntPtr token);

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool CloseHandle(IntPtr handle);

Advapi32.dll contains the LogonUser function which attempts to log on a user to a local computer. The most important arguments which need to be passed to this function are a username, domain and password. The function returns a boolean value indicating if the logon was successful. A handle is passed (by reference), the handle is very important because it can be used to create a new windows identity.

After that you’ll also need a couple of enumerations which can be used as arguments for the LogonUser function as well. Add the following code to your web part class:

enum LogonTypes : uint
{
   Interactive = 2,
   Network,
   Batch,
   Service,
   NetworkCleartext = 8,
   NewCredentials
}

enum LogonProviders : uint
{
   Default = 0, // default for platform (use this!)
   WinNT35, // sends smoke signals to authority
   WinNT40, // uses NTLM
   WinNT50 // negotiates Kerb or NTLM
}

By the way, it’s better to use the Network logon type instead of the Interactive type because of performance reasons and the elimination of the call to DuplicateToken (MS KB article 306158).

Kernel32.dll contains the CloseHandle function which closes an open object handle. This function is used to close the handle which was the result of the LogonUser call.

The following code shows how to override the RenderWebPart method to impersonate users via the LogonUser API call:

protected override void RenderWebPart(HtmlTextWriter output)
{
 string strValue = String.Empty;

 try
 {
   WindowsImpersonationContext objUserContext;
   IntPtr objToken;
   WindowsIdentity objOrgIdentity;
   WindowsIdentity objIdentity;

   bool blnReturn = LogonUser(@"myadministrator", "mydomain", "myadminpassword",
   LogonTypes.Interactive,
   LogonProviders.Default,
   out objToken);

   if ( blnReturn )
   {
      objOrgIdentity = WindowsIdentity.GetCurrent();
      objIdentity = new WindowsIdentity(objToken);
      objUserContext = objIdentity.Impersonate();

      SPSite objSite = SPControl.GetContextSite(Context);
      SPGlobalAdmin objAdmin = new SPGlobalAdmin();
      SPVirtualServer objServer = objAdmin.OpenVirtualServer(new Uri(objSite.Url));
      objServer.CatchAccessDeniedException = false;

      strValue += "url: " + objServer.Url + " virtual server id: " +
      objServer.VirtualServerId + "<br/><br/>";

      strValue += "Identity name after impersonation: " + " " +
      objIdentity.Name + "<br/>";
      objUserContext.Undo();
      strValue += "Indentity name when impersonation is undone: " +
      objOrgIdentity.Name;

      CloseHandle(objToken);
   }
   else
   {
      strValue = "Logon failed!";
   }

 }
 catch (Exception err)
 {
   strValue = "Error in wp: " + err.Message;
 }
 output.Write(strValue);
}

So, to sum up, this is the Ego approach, where the current user context is replaced with a new context.

Id

One of the complaints we hear about the Ego approach is that user credentials are stored in the code which is, and rightfully so, considered unsafe. Well, you shouldn’t store passwords in plain text in code, it’s as simple as that, but there are ways to avoid this. You could save the password in a config file after encrypting it using DPAPI. Another nice solution would be to use SharePoint Single Sign On (SSO) and store the credentials in the credential mapping database.

There is another solution, using credential-less impersonation, which we’ve named the ‘Id’ approach. This approach is explained in detail in a very informative article called ‘Secure SharePoint Code Using Credential-less Impersonation’ by Todd Bleeker.

Basically, a call is made to the Win32 API RevertToSelf function which terminates the impersonation of a client application. SharePoint uses Internet Information Server 6 (IIS) and IIS 6 uses application pool identities as the context in which worker processes run. It’s possible to revert back from a current user context (which is itself an impersonated identity) to the original identity, which is the application pool identity.

The credentials of the application pool identity are stored safely in the IIS metabase. SharePoint requires the application pool identity to be a local administrator and administrator of the SharePoint content database. So, by dumping the current context and reverting to the application pool’s security context it’s possible to do stuff that requires an extensive set of privileges while avoiding storing credentials in code.

To make this possible you’ll need to import the Advapi32.dll. Add the following code to your web part class definition:

[DllImport("advapi32.dll")]
static extern bool RevertToSelf();

As you can see, the RevertToSelf function isn’t hard to use. The following code shows how to override the RenderWebPart method to revert back to the application pool’s security context:

protected override void RenderWebPart(HtmlTextWriter output)
{
 string strValue = String.Empty;

 try
 {
    WindowsIdentity objOriginalUser = WindowsIdentity.GetCurrent();
    RevertToSelf();

    SPSite objSite = SPControl.GetContextSite(Context);
    SPGlobalAdmin objAdmin = new SPGlobalAdmin();
    SPVirtualServer objServer = objAdmin.OpenVirtualServer(new
    Uri(objSite.Url));
    objServer.CatchAccessDeniedException = false;
    strValue += "url: " + objServer.Url + " virtual server id: " +
    objServer.VirtualServerId + "<br/><br/>";

    strValue += "application pool identity name: " +
    WindowsIdentity.GetCurrent().Name + "<br/>";
    WindowsImpersonationContext objContext =
    objOriginalUser.Impersonate();
    strValue += "original user name: " +
    WindowsIdentity.GetCurrent().Name;
  }
  catch (Exception err)
  {
    strValue = "Error in wp: " + err.Message;
  }
  output.Write(strValue);
}

In the Id approach the current user context is reverted back to the underlying original security context. The big difference with the Ego approach is you don’t need to find a way or place to store credentials of a superuser. Also check out the following link: http://mindsharpblogs.com/todd/archive/2005/05/03/467.aspx , this small article explains a way to do impersonation code in a way that requires a smaller set of security privileges.

Superego

With two approaches to impersonate user accounts, who needs a third way? Enter Maurice Prather… On his website Blue Dog Limited he discusses a third way of doing impersonation (for details see http://www.bluedoglimited.com/SharePointThoughts/ViewPost.aspx?ID=7 ). He discusses how sometimes calls to the SharePoint object model will fail if you’re not an administrator, even after you’ve impersonated another user identity or reverted to the application pool identity. The reason for this is that the SharePoint object model, when running under the context of an IIS request, will validate it’s actions against the original context of the request. This renders the Id and Ego approaches described earlier useless, and that is the tricky part, in some scenario’s, because SharePoint uses the orignal user context. The problem is that, as far as we know, there is no way to predict beforehand if impersonation using the Id and Ego approach will work or not. Blue Dog Limited contains a code sample which will fail in those scenario’s. A work around exists and we’ll call it the ‘Superego’ approach.

Basically the Superego approach consists of a number of steps:

  1. Revert to self or impersonate (use either the Id or Ego approach to perform impersonation).
  2. Create an AppDomain.
  3. Execute the work for which you need an extensive set of priviliges in the new child AppDomain.
  4. Marshall back the results.
  5. Unload the AppDomain.
  6. Resume the orginal identity.

The first step, using either the Id and Ego approach, is discussed in detail in previous sections of this article. To understand the rest of the steps you need to understand a couple of things about AppDomains.

AppDomains are very similar to operating system (OS) processes in that they form the fundamental scope for execution of code and ownership of resources. There’s a big difference however, OS processes are abstractions created by the operating system, AppDomains are abstractions created by the Common Language Runtime (CLR). Any AppDomain resides in exactly one OS process whereas one OS process can host multiple AppDomains. Objects always reside in exactly one AppDomain and object references must always refer to objects within the same AppDomain.

It’s quite easy to create and destroy AppDomains programmatically by calling:

AppDomain objAppChild = AppDomain.CreateDomain(“MyChild”);
AppDomain.UnLoad(objAppChild);

Now at some point you would want to load and execute code in a freshly created AppDomain and retrieve the results. The following code shows how to do that:

Object obj = objMyAppDomain.CreateInstanceAndUnwrap(“MyAssembly”, “MyType”);
MyClass obj2 = (MyClass) obj;
String strValue = obj2.DoSomething();

We’ll get to the Unwrap bit later, but let’s first look at the obj2.DoSomething() call. It’s impossible to make an object reference refer to objects which reside in other AppDomains, so how does this work?

If we want to transfer an object reference to another AppDomain it has to be marshalled. Not every type you’ll create can be marshalled. If you create your own class, by default it will be a remote-unaware type, which means it can’t be marshalled to another AppDomain. If you mark a type as being [Serializable] it’s called un Unbound type. Unbound types will be marshalled by value, the CLR will hand a disconnected clone to the receiving AppDomain. Then there’s a third type, the AppDomain-bound type. In this scenario the type is marshalled by reference, the CLR will give the receiving AppDomain a proxy object that forwards all calls back to the original object which resides in the source AppDomain.

AppDomain-bound types derive from System.MarshalByRefObject, either directly or indirectly. For our example we’ve created an AppDomain-bound class called MyClass which performs some operations using the SharePoint administration object model. It’s definition looks like this:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace SeveralLib.Impersonator
{
 ///
 /// Summary description for MyClass.
 ///

 public class MyClass : MarshalByRefObject
 {
  public MyClass()
  {
    //
    // TODO: Add constructor logic here
    //
  }

  public string GetSomeValue()
  {
    string strReturn = String.Empty;

    SPSite objSite = new SPSite("http://myportal");
    SPGlobalAdmin objAdmin = new SPGlobalAdmin();
    SPVirtualServer objServer = objAdmin.OpenVirtualServer(new Uri(objSite.Url));
    objServer.CatchAccessDeniedException = false;
    strReturn += "url: " + objServer.Url + " server id: " + objServer.VirtualServerId;
   
    return strReturn;
  }
 }
}

There’s one important thing we didn’t discuss yet about the CreateInstance call. Please look at the following piece of code:

ObjectHandle objHandle = objMyAppDomain.CreateInstance(“MyAssembly”, “MyType”)
Object objProxy = objHandle.Unwrap();

The CreateInstance method returns an object handle, not a real object reference. The type is not loaded into the AppDomain until Unwrap() is called. This is efficient in scenario’s where two new child AppDomains are created. The parent AppDomain could pass an object handle to an object in AppDomain child1 to AppDomain child2 without requiring the type of the object to be loaded in the parent AppDomain. However, this does mean that you’ll need to call the Unwrap() method before actually using an object.

Another interesting question is where the CLR will look when it tries to load the assembly when calling CreateInstance. Of course it’s possible to deploy your assembly in the Global Assembly Cache (GAC), but it’s possible to specify other locations. Every AppDomain has a SetupInformation property which exposes data which controls the behavior of the assembly resolver. In our example we’ll just copy this information from the current AppDomain:

AppDomainSetup objAppDomainSetup = AppDomain.CurrentDomain.SetupInformation;

This makes it possible to deploy our assembly in the [driveletter]:\inetpub\wwwroot\bin folder. There’s one catch, you need to do this before the AppDomain is created, otherwise you can’t change the AppDomainSetup information for the AppDomain anymore.

We also want to provide our new AppDomain with Evidence information. We’ll copy that information from the current AppDomain too:

Evidence objEvidence = AppDomain.CurrentDomain.Evidence;

If you add the following code to the RenderWebPart method you can try the Superego approach for yourself.

// Do impersonation or revert to self
// Id or Ego code goes here.

AppDomainSetup objAppDomainSetup = AppDomain.CurrentDomain.SetupInformation;

Evidence objEvidence = AppDomain.CurrentDomain.Evidence;

AppDomain objMyAppDomain = AppDomain.CreateDomain("MyAppDomain", objEvidence, objAppDomainSetup);

try
{
  strValue += objMyAppDomain.SetupInformation.ApplicationBase;
  strValue += " probe: " + objMyAppDomain.SetupInformation.PrivateBinPathProbe;
  strValue += " prviate bin: " + objMyAppDomain.SetupInformation.PrivateBinPath;
  ObjectHandle objHandle = objMyAppDomain.CreateInstance(Assembly.GetExecutingAssembly().GetName().FullName, typeof(MyClass).FullName);

  Object objProxy = objHandle.Unwrap();
  MyClass objClass = (MyClass) objProxy;
  strValue += "<br/><br/>" + objClass.GetSomeValue();
}
catch (Exception err)
{
  strValue += err.Message;
}
finally
{
  AppDomain.Unload(objMyAppDomain);
}

strValue += "app pool name: " + WindowsIdentity.GetCurrent().Name + "<br/>";

// Restore original context
// Id or Ego code goes here.

In the Superego approach we create an AppDomain to perform high priviliged work for us.

Conclusion

We’ve discussed three different ways of user impersonation. The Ego approach is flexible and let’s you choose the user account you want to impersonate. However, if you use this approach you’ll need to find a safe data store for the credentials of your super user account. The Id approach provides an easy way to perform code which needs an extensive set of privileges, offers a safe store for user credentials by default, but lacks the flexibility offered in the Ego approach. The Superego approach is the most complex approach, but it’s the only scenario which is guaranteed to work all of the time. Take your pick!

« back to overview page