Monday, April 16, 2012

Getting Elevated Search Results in SharePoint 2010

I often use the SharePoint 2010 search CoreResultsWebPart in combination with scopes, content types and managed properties defined in the Search Service Application (SSA) for having dynamic search-driven content in pages. Sometimes the users might need to see some excerpt of content that they really do not have access to, and that you don't want to grant them access to either; e.g. to show a summary to anonymous visitors on your public web-site from selected content that is really stored in the extranet web-application in the SharePoint farm.

What is needed then is to execute the search query with elevated privileges using a custom core results web-part. As my colleague Mikael Svenson shows in Doing blended search results in SharePoint–Part 2: The Custom CoreResultsWebPart Way, it is quite easy to get at the search results code and use the SharedQueryManager object that actually runs the query. Create a web-part that inherits the ootb web-part and override the GetXPathNavigator method like this:

namespace Puzzlepart.SharePoint.Presentation
{
    [ToolboxItemAttribute(false)]
    public class JobPostingCoreResultsWebPart : CoreResultsWebPart
    {
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
        }
 
        protected override XPathNavigator GetXPathNavigator(string viewPath)
        {
            XmlDocument xmlDocument = null;
            QueryManager queryManager = 
              SharedQueryManager.GetInstance(Page, QueryNumber)
                .QueryManager;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                xmlDocument = queryManager.GetResults(queryManager[0]);
            });
            XPathNavigator xPathNavigator = xmlDocument.CreateNavigator();
            return xPathNavigator;
        }
    }
}

Running the query with elevated privileges means that it can return any content that the app-pool identity has access to. Thus, it is important that you grant that account read permissions only on content that you would want just any user to see. Remember that the security trimming is done at query time, not at crawl time, with standard SP2010 server search. It is the credentials passed to the location's SSA proxy that is used for the security trimming. Use WindowsIdentity.GetCurrent() from the System.Security.Principal namespace if you need to get at the app-pool account from your code.

You would want to add a scope and/or some fixed keywords to the query in the code before getting the results, in order to prevent malicious or accidental misuse of the elevated web-part to search for just anything in the crawled content of the associated SSA that the app-pool identity has access to. Another alternative is to run the query under another identity than the app-pool account by using real Windows impersonation in combination with the Secure Store Service (see this post for all the needed code) as this allows for using a specific content query account.

The nice thing about using the built-in query manager this way, rather than running your own KeywordQuery and providing your own result XML local to the custom web-part instance, is that the shared QueryManager's Location object will get its Result XML document populated. This is important for the correct behavior for the other search web-parts on the page using the same QueryNumber / UserQuery, such as the paging and refiners web-parts.

The result XmlDocument will also be in the correct format with lower case column names, correct hit highlighting data, correct date formatting, duplicate trimming, getting <path> to be <url> and <urlEncoded>, have the correct additional managed and crawled properties in the result such as <FileExtension> and <ows_MetadataFacetInfo>, etc, in addition to having the row <id> element and <imageUrl> added to each result. If you override by using a replacement KeywordQuery you must also implement code to apply appended query, fixed query, scope, result properties, sorting and paging yourself to gain full fidelity for your custom query web-part configuration.

If you don't get the expected elevated result set in your farm (I've only tested this on STS claims based web-apps; also see ForceClaimACLs for the SSA by my colleague Ole Kristian Mørch-Storstein), then the sure thing is to create a new QueryManager instance within the RWEP block as shown in How to: Use the QueryManager class to query SharePoint 2010 Enterprise Search by Corey Roth. This will give you correctly formatted XML results, but note that the search web-parts might set the $ShowMessage xsl:param to true, tricking the XSLT rendering into show the "no results" message and advice texts. Just change the XSLT to call either dvt_1.body or dvt_1.empty templates based on the TotalResults count in the XML rather than the parameter. Use the <xmp> trick to validate that there are results in the XML that all the search web-parts consumes, including core results and refinement panel.

The formatting and layout of the search results is as usual controlled by overriding the result XSLT. This includes the data such as any links in the results, as you don't want the users to click on links that just will give them access denied errors.

When using the search box web-part, use the contextual scope option for the scopes dropdown with care. The ContextualScopeUrl (u=) parameter will default to the current web-application, causing an empty result set when using the custom core results web-part against a content source from another SharePoint web-application.

18 comments:

Unknown said...

Nice! Thx alot!

Just 1 question...
I want to know if there is any difference between the 'normal' search results and the ones with elevated privileges. But it seems when you try to execute 'queryManager.GetResults(queryManager[0]);' with elevated privileges the result is null.

Any advice?

Kind regards,
Maxim

Kjell-Sverre Jerijærvi said...

The results should be the same as when doing a normal search when logged on as the app-pool account.

Still, I've seen the null result in one of our test farms, but not diagnosed why. Please check the tips provided here: http://www.threewill.com/2010/06/connect-to-sharepoint-forwarding-user-identities/

Hugo Molina said...

I've tried your code in one of our webparts, and it dosen't seem to work, I keep getting only the results the curent user has access to, I was wondering if you could give me a hand figuring out whats wrong whit our webpart.

Thanks!

Kjell-Sverre Jerijærvi said...

We're about to deploy and test on a fourth farm next week, this will decide for us if our app-pool identy config works for RWEP and passing the correct claims for the SSA proxy, that is the Location.Credentials for a SP2010 SSA. If not, we will use a KeywordQuery instead as we know for sure that that works all the time.

You could also try faking the HttpContext and SPContext within the RWEP as this workaround is sometimes needed when calling SP objects using a specific identity.

Kjell-Sverre Jerijærvi said...

And you can always use a new instance of the QueryManager class within RWEP to get the search results in the expected XML format. See this post: http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2010/08/15/how-to-use-the-querymanager-class-to-query-sharepoint-2010-enterprise-search.aspx

Kjell-Sverre Jerijærvi said...

So now we've tested this on several development single server farms with Windows authN, and on the test, staging and production farms with ADFS claims-based authN; and running the query with elevated privileges works on all but one CBA farm.

Why? don't know, and don't have the time to find out why.

Kjell-Sverre Jerijærvi said...

Now it works in all our SharePoint 2010 CBA farms! My colleague Ole Kristian Mørch-Storstein provided me with the solution from a similar issue he'd solved using "ForceClaimACLs" for the SSA when getting no results for specific accounts: http://toastertech.com/2012/06/sharepoint-2010-crawler-indexes-items-but-they-are-not-searchable/

bdurfee said...

This works great. However, since I am using the Search Paging and Search Statistics web parts, I am assuming that the above changes similarly need to be made for those two web parts. Is that correct?

Kjell-Sverre Jerijærvi said...

We've used the refiner and pager web-parts, they both work correctly without any modifications. See this site for a example of searching with elevated privileges: http://www.uloba.no/jobb/Sider/jobb-listing.aspx

bdurfee said...

Hmmm...I went there and search for "asssitent". The search statistics showed: 1-8 av 8 treff. However, the search returned 9 results. This is similar to the problem I am having.

Kjell-Sverre Jerijærvi said...

How did you search for "assistent" and where did you see the search statistics on the page?

bdurfee said...

http://www.uloba.no/sok/results.aspx?k=assistent

Kjell-Sverre Jerijærvi said...

That is not the URL to the search-driven job listing page that I provided.

You're using the standard search page, and the 9th result is the "best bet" featured page for the keyword you entered, beneath that is the 8 actual results.

bdurfee said...

OK. I understand now. I didn't understand what you were showing at first (I don't speak/read Norwegian). I do have a new problem that is somewhat vexing. The code runs in my development environment just fine. However, when I add the solution to my QA or Production sites, I get a null reference exception on xmlDocument. I am assuming it may be permission related, but I have been working on it for several hours I am at my wits end. Can you provide any insight? Thank you in advance.

Kjell-Sverre Jerijærvi said...

We had the same problem in one of our five farms, null results - but when logged in you would get results for the exact same SSA scope. The solution to our problem was the ForceClaimACLs fix linked to in this blog post.

Rathi said...

Hi i tried thhis iin my development server.it is giving same result based on useer access not using elevated permission.

can u help me?

Kjell-Sverre Jerijærvi said...

All solutions to this issue that I know of is listed in the above text.

Ruslan Inozemtsev said...

Sorry guys, but i thing this is not working sample.
When we go to overriden GetXPathNavigator method and see queryManager[0] object in debugger we can see that ReturnedResults property contain number of results depended from current user rights. That is mean that query already execuded in this point. And we can manipulate with permissions there