A common difficulty with SPSecurity.RunWithElevatedPrivileges
(most recently lamented here) is that existing SPSite
/SPWeb
objects will not work with the elevated security context. The natural workaround is to create a new SPSite
within the elevated delegate and off you go. However, Dan Larson has documented a better technique here, leveraging the SPSite
constructor that accepts an SPUserToken
. By passing the token of the system account (SHAREPOINT\system, an internal alias for the application pool identity), an elevated site is available without the complexity and complications of RunWithElevatedPrivileges
.
With the emerging best practice of separating SPSite
and SPWeb
creation from the code that uses them, I have expanded on Dan’s technique with another series of extension methods. The first step is to get the system token from an existing SPSite
, adapted from Dan’s example:
public static SPUserToken GetSystemToken(this SPSite site) { SPUserToken token = null; bool tempCADE = site.CatchAccessDeniedException; try { site.CatchAccessDeniedException = false; token = site.SystemAccount.UserToken; } catch (UnauthorizedAccessException) { SPSecurity.RunWithElevatedPrivileges(() => { using (SPSite elevSite = new SPSite(site.ID)) token = elevSite.SystemAccount.UserToken; }); } finally { site.CatchAccessDeniedException = tempCADE; } return token; }
And then we simply provide methods to operate on an elevated site created with that token:
public static void RunAsSystem(this SPSite site, Action<SPSite> action) { using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken())) action(elevSite); } public static T SelectAsSystem<T>(this SPSite site, Func<SPSite, T> selector) { using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken())) return selector(elevSite); }
Note that the object(s) returned by selector
should not be SharePoint objects that hold references to a parent site/web.
It’s also useful to provide additional methods that simplify working with elevated webs:
public static void RunAsSystem(this SPSite site, Guid webId, Action<SPWeb> action) { site.RunAsSystem(s => action(s.OpenWeb(webId))); } public static void RunAsSystem(this SPSite site, string url, Action<SPWeb> action) { site.RunAsSystem(s => action(s.OpenWeb(url))); } public static void RunAsSystem(this SPWeb web, Action<SPWeb> action) { web.Site.RunAsSystem(web.ID, action); } public static T SelectAsSystem<T>(this SPSite site, Guid webId, Func<SPWeb, T> selector) { return site.SelectAsSystem(s => selector(s.OpenWeb(webId))); } public static T SelectAsSystem<T>(this SPSite site, string url, Func<SPWeb, T> selector) { return site.SelectAsSystem(s => selector(s.OpenWeb(url))); } public static T SelectAsSystem<T>(this SPWeb web, Func<SPWeb, T> selector) { return web.Site.SelectAsSystem(web.ID, selector); }
I don’t bother to Dispose
the webs created by these calls to s.OpenWeb()
because I know s
will be disposed shortly, cleaning up the web with it.
Usage
As an example, let’s refactor Nigel’s skeleton code:
protected override void CreateChildControls() { SPWeb web = SPControl.GetContextWeb(Context); web.RunAsSystem(UpdateVisitorList); } private void UpdateVisitorList(SPWeb web) { SPList visList = web.Lists[_visitorList]; int existItemId = GetVisitorItemId(visList); web.AllowUnsafeUpdates = true; if (existItemId < 1) { SPListItem newItem = visList.Items.Add(); // Set field values newItem.Update(); } else { SPListItem oldItem = visList.GetItemById(existItemId); // Update field values oldItem.Update(); } web.AllowUnsafeUpdates = false; }
By separating the elevated operation from the logic required to elevate, the code is much easier to read and less prone to errors like leaking an elevated SPSite
.
January 6, 2009 at 5:01 am
Hi Keith,
This seem a much cleaner and safer way to achieve the elevated privileges to me. Will look at refactoring my code, and adding the “RunAsSystem” funtionality to my SharePoint.Common library asap :)
Many thanks for the tips!
Regards,
Nigel
January 8, 2009 at 6:09 pm
[…] Elegant SPSite Elevation […]
January 16, 2009 at 4:59 am
Keith,
this is really elegant! I am new to extensions, and hand problems in getting the SelectAsSystem extension to work. I got some compiler errors on my first attempt, and finally came up with this solution:
SPSite site = SPContext.Current.Site;
string result = site.SelectAsSystem((Func<SPSite, string>)SomeFunction);
public string SomeFunction(SPSite site)
{
…
}
I’m not sure if this would be considered as elegant… ;-)
Do you have a better solution?
Thanks in advance!
Alex
January 16, 2009 at 9:27 am
Hi Alex ~
There are a number of compiler errors that you could get when working with lambdas. Maybe not all code paths returned an object? Maybe the type of the returned object couldn’t be inferred? Did you name the delegate’s argument “site”, which would conflict with the previous definition of site?
This refactoring of your code should work fine, assuming the error isn’t coming from “…”:
SPSite contextSite = SPContext.Current.Site;
string result = contextSite.SelectAsSystem(site =>
{
…
});
If you’re still having problems, feel free to post the code that you expect to compile. Note that WordPress likes to eat < >, so make sure you escape them: <
Cheers ~
Keith
January 16, 2009 at 9:38 am
Hi Keith,
thanks for the quick reply, and thanks for this great article!
My method is like the SomeFunction() mentioned above. I thought I should be possible to invoke it like this:
string result = site.SelectAsSystem(SomeFunction);
The compiler error was:
The type arguments for method ‘MyProject.SPSecurityExtensions.SelectAsSystem<T>(Microsoft.SharePoint.SPSite, System.Func<Microsoft.SharePoint.SPSite,T>)’ cannot be inferred from the usage. Try specifying the type arguments explicitly.”
So I thought I should type cast my method as mentioned in my first post, and it seems to work (need to test it further)
Thanks again!
Alex
January 16, 2009 at 10:48 am
Glad to help! I’ve never noticed that specific behavior, but you are indeed correct. Rather than cast the delegate, a slightly cleaner option is to specify the type on the method call instead:
string result = site.SelectAsSystem<string>(SomeFunction);
As to why the compiler can’t infer the type of methods, the short answer is overloads. Note the wording on the error if you try to set var f = SomeFunction: “Cannot assign method group to an implicitly-typed local variable.” Overloads don’t apply here since our Func can only accept an SPSite, but in general the problem is hard enough that the compiler doesn’t even try. This also explains why lambdas and anonymous methods can be passed in without specifying the type – they can’t have overloads!
Hope this helps ~
Keith
January 20, 2009 at 2:27 am
[…] the functionality in my SharePoint.Common library) and recommend reading his article – see https://solutionizing.net/2009/01/06/elegant-spsite-elevation/Keep up the good work […]
January 20, 2009 at 10:12 am
Keith,
thanks again, I’ve never been using the Extensions, but this is an example of the real they provide!
I’m now using this method, as it has multiple advantages:
it’s elegant, and it is easy to pick up the actual user in the priveleged code (e.g. from the thread)
A tip: if you need to do something with elevated priveleges and pass multiple parameters, a simple workaround is to create a helper class:
class Helper
{
private params …
public Helper(params…)
{
store params in helper class
…
}
public DoIt(SPWeb web)
{
…
}
}
and in the main code:
Helper h = new Helper(params…)
web.RunAsSystem(h.DoIt);
Cheers,
Alex
February 19, 2009 at 9:13 pm
Im not totally convinced that using SPUserToken is “better” than RunWithElevatedPrivileges.
As an example try this:
SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite site = new SPSite(“http://sharepoint”)) {
using (SPWeb web = site.OpenWeb()) {
SPList list = web.Lists[“Test List”];
SPListItem item = list.Items.Add();
item[“Title”] = “Title 1”;
item.Update();
}
}
});
SPUserToken token;
using (SPSite csite = new SPSite(“http://sharepoint”)) {
token = csite.SystemAccount.UserToken;
}
using (SPSite site = new SPSite(“http://sharepoint”, token)) {
using (SPWeb web = site.OpenWeb()) {
SPList list = web.Lists[“Test List”];
SPListItem item = list.Items.Add();
item[“Title”] = “Title 2”;
item.Update();
}
}
Then look at the item properties. See how the one created with RunWithElevatedPrivileges has the context user as the creator. But the one created with the token has the system account as the creator. As long as you know there are differences between the two methods.
May 28, 2009 at 12:38 pm
djeeg, I am using your method of the delegate RunWithElevatedPrivileges in a WebPart that does UNC file access to build a explorer type tree in the web part. It deploys and works fine on our test WSS 3.0 server farm, but when I deploy to our production farm, it seems to deploy and install normally. But when you try to add the web part to any page you get the import error message (“Cannot import web part”). Any ideas on why? I have beat myself to death on the usual suspects, Version number, public key, is the dll in the GAC, etc. It seems to be something about the RunWithElevatedPrivileges.
May 28, 2009 at 1:55 pm
The ULS logs (12\LOGS by default) should show you exception details to help pinpoint the error. One “usual suspect” you didn’t list would be a SafeControl entry. Other than that, my first guess would be that your production application pool identity doesn’t have sufficient permissions on the file share.
February 25, 2009 at 3:36 pm
djeeg ~
Judging by the lack of web.AllowUnsafeUpdates, I’m guessing you tried that from a console application rather than in a web context. If the current process doesn’t use impersonation (console app, stsadm, timer job), RWEP does nothing, which is why you would see the context user as the creator.
To see the real difference between the two methods, stick your code in a webpart and set title to Environment.Username. The RWEP block will show the app pool username, the other will show the current user, and both items will be created by System Account.
Cheers ~
Keith
March 19, 2009 at 12:40 am
[…] Elegant SPSite Elevation […]
June 1, 2009 at 1:22 am
[…] Elegant SPSite Elevation […]
November 6, 2009 at 2:45 am
[…] Practices for Elevated Privilege in SharePoint and Elevated Privilege with SPSite by Daniel Larson Elegant SPSite Elevation by Keith […]
July 26, 2010 at 1:43 pm
I am still getting access denied error while trying to change site quota impersonating systemaccount. When I login as system account the code works. any idea why? I have already setup oweb.allowunsafeupdates to true.
October 29, 2010 at 11:20 am
Thank you! GetSystemToken saved my ass today!
December 24, 2010 at 8:48 am
[…] using a UserToken: https://solutionizing.net/2009/01/06/elegant-spsite-elevation/ or […]
June 24, 2011 at 7:52 am
Keith,
Over 2 years later this post is still helpful :) Great samples and technique..just used it to fix a nasty problem that was giving us lots of headaches. Hope to see you at Day of .NET this year!
June 29, 2012 at 11:13 am
Reading the MSDN articles, it seems that they’re recommending use of:
static property SPUserToken.SystemAccount
instead of looping through SPSite.SystemAccount.UserToken