Saturday, March 9, 2013

Search Query Suggestions for anonymous users in SharePoint 2013–and with security trimming

Query suggestions have improved in SharePoint 2013 and in addition to showing popular queries it can also show people matching your query or popular queries you have executed yourself in the past. But for anonymous users this is not the case.

image

Out of the box, query suggestions will not work for anonymous users as Waldek Mastykarz wrote about back in December 2012. Waldek actually contacted me before writing his post and as I commented on his post, this is due to some hardcoding in the internal code logic in Microsoft.Office.Server.Search.Query.Query

GetQuerySuggestionsWithResults(…)
.
.
if (SearchCommon.IsUserAnonymous)
{
    return new QuerySuggestionResults(new QuerySuggestionQuery[0], new PersonalResultSuggestion[0], new string[0]);
}



In addition there is no security trimming of search suggestions, meaning that you can get suggestions which you will get no hits for – potentially a security hole. This is useful both for anonymous and logged in users, and might be why they have turned off suggestions for anonymous users by default.

While Waldek created his own solution and storing search suggestions in a custom list, I decided to rework the existing logic, and with javascript replace the exiting calling query with a custom one, still using SharePoint’s query suggestion engine.

The first step is to be able to get query suggestions without being blocked by the IsAnonymous user check. This is done by calling GetQuerySuggestionsWithResults() on the ISearchServiceApplication interface. This takes us deep enough to avoid the anonymous check.

If you want to security filter the suggestions you have to execute a search per suggestion and check if you get any results back. Fortunately 2013 allows the execution of multiple queries in parallel, so this will speed up the checking somewhat.. .but bear in mind that the checking can impact your search performance if it can’t handle the extra load.

foreach (var suggestion in queries)
{
    KeywordQuery q = new KeywordQuery(web)
    {
        QueryText = ReCleanTags.Replace(suggestion.Query, string.Empty),
        SourceId = sourceId,
        RowLimit = 1,
        EnableStemming = true,
        UserContextGroupID = web.ID.ToString(),
        Culture = culture,
        EnableQueryRules = true
    };
    q.SelectProperties.Clear();
    keywordQueries.Add(suggestion.Query, q);
}
SearchExecutor se = new SearchExecutor();
var results = se.ExecuteQueries(keywordQueries, true);

For overriding the existing ajax call to get query suggestions I have created a custom masterpage which loads my script, and upon loading it will overload

AjaxControlToolkit.AutoCompleteBehavior.prototype._getSuggestion

with a custom method using jQuery to execute the suggestion call against a custom protocol handler. The script can be added in many other ways, but it shows the concept.

The protocol handler has a convenience method to turn security trimming on and off by adding the trimsuggestions parameter which will set a property named mAdcOWQuerySuggestions_TrimSuggestions on the web, storing the true or false value, having it false by default.

http://intranet.madcow.local/sites/searchcenter/_vti_bin/SearchSuggestions.ashx?trimsuggestions=true

If you want to further improve search suggestions you can include live search results or images of the people suggestions. Each suggestion is basically an HTML string, and you can modify it any way you want with custom markup for cool layouts.

Pre-build WSP as well as the code can be found at http://spsearchparts.codeplex.com/

Good luck!