Tuesday, July 22, 2008

SharePoint Elevated Privilege without RunWithElevatedPrivelege

You may need to elevate privilege if the current user doesn't have permission to read that object from the SPSite. In this case, you'll need to elevate privilege just to get the user token, but we don't want to perform any operations inside RunWithElevatedPrivilege, we only want to get a token out (which is basically a simple byte array). You could also cache the system token in the application if you needed to.
Best Practices for Elevated Privilege in SharePoint:
Elevated Privilege can be used to bypass or work with security, and can be performed either through SPSecurity or through impersonation techniques involving the SPUserToken and the SPSite class. It's one of the most misunderstood aspects of the SharePoint API, but in general you should always prefer impersonation using the SPSite class and SPUserToken objects. Here's my list of best practices for elevated privilege code in SharePoint that will help you create more reliable applications for the enterprise.
• Avoid using SPSecurity.RunwithElevatedPrivilege to access the SharePoint object model. Instead, use the SPUserToken to impersonate with SPSite.
• If you do use SPSecurity.RunwithElevatedPrivilege, dispose of all objects in the delegate. Do not pass SharePoint objects out of the RunwithElevatedPrivilege method.
• Only use SPSecurity.RunwithElevatedPrivilege to make network calls under the application pool identity. Don't use it for elevation of privilege of SharePoint objects.
• Always use the SPSite constructor with an SPUserToken to create an elevated privilege security context in SharePoint. To impersonate the system, use the SystemAccount.UserToken property of the current SPSite context, such as:
var site = new SPSite(SPContext.Current.Site.ID, SPContext.Current.Site.SystemAccount.UserToken);
• Avoid passing SharePoint objects between different security contexts (SPSite instances), with the exception of the SPUserToken used in the SPSite ctor. An SPUser object created from SPSite A cannot (reliably) be passed to SPSite B. This can be the source of obscure bugs in production that are difficult to reproduce in development. For example, an SPUser reference created from SPContext.Current.Site cannot reliably be used in an elevated site context, as the user reference may take on a different meaning in the alternate context.
• Never use elevated privilege to bypass security-- always use it to work with security.
• Restrict what assemblies can use elevated privilege by running in minimal trust, avoiding the GAC, and auditing any CAS policies deployed with vendor solutions.
A better way to do perform system actions is to impersonate the SHAREPOINT\system account. Impersonation is a concept that is built into the object model, but is underutilized by developers.
The SPSite object takes an SPUserToken object in its constructor in order to support impersonation. (This does require Impersonate="True" for the Microsoft.SharePoint.Security.SharePointPermission permission class). You can impersonate any user when creating the SPSite context-- so to get the system account, just use the magic system account "SHAREPOINT\system".
Here's a code sample of SYSTEM ACCOUNT impersonation. The SYSTEM ACCOUNT uses the login name "SHAREPOINT\system" internally while it will use the process identity (usually that means the application pool identity, but it could be the service identity if a task was run as a timer job) when making external network calls. Note that the account is abstracted, and when a request comes into the system AS this account it will take on the identity of SYSTEM ACCOUNT.
Here's the code sample. Grab a user object, and then grab the SPUserToken for impersonation:

var user = SPContext.Current.Web.AllUsers[@"SHAREPOINT\SYSTEM"];
var superToken = user.UserToken;
using (var site = new SPSite(SPContext.Current.Web.Url, superToken))
{
// This code runs under the security context of the SHAREPOINT\system
// for all objects accessed through the "site" reference. Note that it's a
// different reference than SPContext.Current.Site.
using(var elevatedWeb = site.OpenWeb())
{
// Perform actions as SYSTEM here
}
}


Because the thread identity hasn't changed, this will produce more stable code in most circumstances, although you should be aware that ONLY objects that are referenced from the elevated site context will run as system.

6 comments:

Unknown said...

Thanks Everyone! The post below helped me out a great deal.

Thanks Everyone! The post below helped me out a great deal.

using System;
using Microsoft.SharePoint;

namespace LitwareSecurity
{
/// <summary>A class for working with elevated privilege</summary>
public static class SpSecurityHelper
{
/// <summary>Returns an elevated site</summary>
/// <param name="theSite">
/// The site that you want an elevated instance of.
/// You must dispose of this object unless it is part of SPContext.Current.
/// </param>
/// <returns>An elevated site context.</returns>
/// <remarks>Be sure to dispose of objects created from this method.</remarks>
public static SPSite GetElevatedSite(SPSite theSite)
{
var sysToken = GetSystemToken(theSite);
return new SPSite(theSite.ID, sysToken);
}

/// <summary>Gets a UserToken for the system account.</summary>
/// <param name="site"></param>
/// <returns>A usertoken for the system account user./returns>
/// <remarks>Use this token to impersonate the system account</remarks>
public static SPUserToken GetSystemToken(SPSite site)
{
site.CatchAccessDeniedException = false;
try {
return site.SystemAccount.UserToken;
}
catch (UnauthorizedAccessException) {
SPUserToken sysToken = null;

// Only use runwithelevated to grab the system user token.
SPSecurity.RunWithElevatedPrivileges(
delegate()
{
using (SPSite lolcatKiller = new SPSite(site.ID)) {
sysToken = lolcatKiller.SystemAccount.UserToken;
}
}
);
return sysToken;
}
}
}
}

Soumya said...

Hi Melvin,

Good to see that this post was helpful to you. I will update my blog with few additional areas in sharepoint which may interest you.

Thanks,
Soumya

Unknown said...

You save my life buddy!

Hannibal said...

I tried this:

SPUser user = SPContext.Current.Web.AllUsers[@"SHAREPOINT\SYSTEM"];

But got the error as:
Object reference not set to an instance of an object.
Any help on this.
Thanks

Ranjani Rajagopalan said...

Hi,

I am getting "The current is not a farm administrator" error . Please help

JP said...

Thanks Soumya for the good code!