Wednesday, May 14, 2008

SharePoint impersonation: Id and Ego

Description:
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:



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:


class="AspNetHostingPermission"

version="1"

Level="Minimal"

/>

version="1"

Unrestricted="true"/>

class="SecurityPermission"

version="1"

Flags="Execution,UnmanagedCode,ControlPrincipal,ControlAppDomain,

ControlEvidence"

/>

version="1"

Connections="True"

/>

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, see http://support.microsoft.com/?kbid=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 + "

";

strValue += "Identity name after impersonation: " + " " +

objIdentity.Name + "
";

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 ( http://sharepointadvisor.com/doc/16238 ).

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 + "

";

strValue += "application pool identity name: " +

WindowsIdentity.GetCurrent().Name + "
";

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.





No comments: