LINQ to SharePoint and RunWithElevatedPrivileges
Recently one of my colleagues faced an issue with LINQ to WebPart while executing the code with elevated permission. It was a WebPart using LINQ to query a list called “Announcements” for which the currently logged on user does not (and should not) have access (therefore the web part runs with elevated privileges).But it was not working because LINQ seems to build some “context” even before the web part runs with elevated privileges so that it throws an exception says that list “Announcements” is not found.
Problematic code:
private Dictionary<string, List<Annoucementlist>> GetAnnouncmentListInfo()
{
try
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite sc = new SPSite(SPContext.Current.Site.Url))
{
using (DemoSiteContext dc = new DemoSiteContext (sc.RootWeb.Url))
{
var q = from annoucements in dc.Announcements // When the currently logged on user does not have permissions on the “Announcements ” list, this crashes
where annoucements.Title == “something”
select annoucements.Title;
}
}
});
}
catch (Exception) // System.ArgumentException List " Announcements " not found!
{
}
return functionalAreas;
}
It was an interesting issue, after researching found that it was because of a constructor call in the class SPServerDataConnection class within the Microsoft.SharePoint.Linq.dll
Culprit: Microsoft.SharePoint.Linq.Provider.SPServerDataConnection class
Once we create a new instance of SharePoint Linq DataContext it actually executes a constructor of SPServerDataConnection class where some funny things happening in behind. If we are executing the code with SPContext then it will be using the SPSite and SPWeb instances under the current context and ie why we are getting the exception.
Here is the internal code of SPServerDataConnection that I got via Reflector.
public void SPServerDataConnection(string url)
{
if (SPContext.Current != null)
{
this.defaultSite = SPContext.Current.Site;
this.defaultWeb = (SPContext.Current.Web.Url == url)
? SPContext.Current.Web
: this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
}
else
{
this.defaultSite = new SPSite(url);
this.defaultWeb = this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
}
if (!this.defaultWeb.Exists)
{
throw new ArgumentException
(Resources.GetString("CannotFindWeb", new object[] { url }));
}
this.defaultWebUrl = this.defaultWeb.ServerRelativeUrl;
this.openedWebs = new Dictionary<string, SPWeb>();
this.openedWebs.Add(this.defaultWebUrl, this.defaultWeb);
}
Work around: we can tweak this functionality by creating a new HttpContext and use it under elevated permission to execute your code. After your usage set the actual SPContext back. (it is a work around by playing with HttpContext J so be careful )
Sample WebPart code given below
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Linq;
using System.Collections.Generic;
using System.Linq;
namespace LinqWebPart.WebPart1
{
[ToolboxItemAttribute(false)]
public class WebPart1 : WebPart
{
protected override void CreateChildControls()
{
string strUrl = SPContext.Current.Web.Url;
HttpContext backupCtxt = HttpContext.Current;
try
{
// if there is a SPContext make it is as null so LINQ won’t execute under current context
if (SPContext.Current != null)
HttpContext.Current = null;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite oSite = new SPSite(strUrl))
{
using (SPWeb oWeb = oSite.OpenWeb())
{
// creating a new HttpContext under elevated permission and setting it as current control context web, because of this the code will be running under elevated permission.
HttpRequest httpRequest = new HttpRequest("", oWeb.Url, "");
HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new System.IO.StringWriter()));
SPControl.SetContextWeb(HttpContext.Current, oWeb);
using (DemositeDataContext dc = new DemositeDataContext(oWeb.Url))
{
var q = from list in dc.Announcements
where list.Title == "My Announcment title"
orderby list.Id
select new { DisplayName = list.Title };
//remaining code goes here...
}
}
}
}
);
}
catch (Exception ex)
{
//Use your favourite form of logging to log the error message and exception ....
}
finally
{
// make sure that you are setting the actual SPContext back after your operation and make SharePoint happy J
if (SPContext.Current != null)
HttpContext.Current = backupCtxt;
}
}
}
}
It is a known design behavior with LINQ to SharePoint.
Related information is documented in the following MSDN article: https://msdn.microsoft.com/en-us/library/ff798485.aspx
Using SPQuery with Regular SharePoint Lists
You should consider using the SPQuery class, instead of LINQ to SharePoint, in the following scenarios:
· When you have anonymous users on your site. LINQ to SharePoint does not support anonymous user access.
Note: |
This limitation exists at the time of publication. However, it may be resolved in future service packs or cumulative updates |
I have tested with Aug CU 2010 and it is not yet changed, it may change in future.
Comments
Anonymous
September 18, 2010
RWEP is not necessary in this scenario. The web part code should use Impersonation via the SPSite.SystemUser.UserToken. www.mindsharp.com/default.aspxAnonymous
September 19, 2010
Important point :We will get full results based on "system user" not filter based on the current user which is always wrong. Audience , Security trimming will fail on this approachAnonymous
September 19, 2010
@Paul - since the SPContext check is implemented inside the SPServerDataConnection I don't think even if we create a SPSite instance by passing the user token will work.Anonymous
September 19, 2010
The comment has been removedAnonymous
November 23, 2011
In the work arround of above issue. we are creating SPSite and SPWeb object to fix this issue so instead of creating datacontext again if we use SPWeb object itself, that will much faster. I think, we should not use LINQ to SharePoint whenever we need to run code with elevated privilege. Correct me if I am wrong.Anonymous
December 06, 2011
We have found using this code causes the CPU to run at 100% for long periods of time. Debugging shows it's related to the use of a static Dictionary object within the LINQ to SharePoint provider and multiple threads access and modify the underlying SPList/data - all threads are stuck in FindEntry() from the Dictionary. I recommend NOT using this workaround until you upgrade to a version of LINQ to SharePoint that supports anonymous access.Anonymous
September 22, 2012
jcapka.blogspot.com.br/.../making-linq-to-sharepoint-work-for.htmlAnonymous
January 15, 2013
Here is the HotFix for this problem. support.microsoft.com/.../2266423