Edit: The code was updated to work correctly when Paging is enabled.
This blog post will guide you through the steps on how to trim the search results based on permissions or roles. Let's start with the use case:
I've got a page which is visible only to authenticated users:
Figure: This page is visible only to Authenticated users.
So, if you are not logged in in the front end you will not see the page in the navigation. Even if you manually type its URL in the address bar you will be first redirected to the login screen. So far so good.
Imagine now that an anonymous user performs a search that returns this particular page as a result. He would be able to see it in the search results and maybe even see some confidential information:
Figure: an Anonymous user performs a search which returns the page he's not supposed to see
Note: the unauthenticated user will not be able to open the page, but still be able to see its title, URL and some of the content.
The steps below will show you how to change the behaviour of the SearchResults widget so that it takes into account the permissions of the individual result items.
1. Create a class in your Visual Studio solution that inherit from the SearchResults class:
2. Override the InitializeControls method:
Here is some explanation: we call the base InitializeControls method because it takes care of calling the search service and binding the results to the ResultsList repeater. The ResultsList repeater is part of the UI that shows the results.
Remember, this results collection is not yet trimmed by permissions, so we need to alter it and include only the items that the current user is allowed to see.
To do that, we iterate through the collection and for each item we check its type. Based on the content type we invoke the appropriate manager and check if the View permission type is granted to the current user. If it is, then we add it to our securedResultSet collection.
At the end we bind the ResultsList repeater to the securedResultSet collection.
Note, you can use the same logic to perform checks on other content types like News, Documents, etc.
Finally, the message that shows the number of the returned results is updated, because the securedResultSet collection might contain less items than the original results collection.
3. Build and Register the new widget using Thunder in the page's toolbox.
4. Drag the new widget to a page and start using it.
That's it. Now the anonymous users will not find the pages they are not supposed to see:
Figure: the search results do not include the page that is hidden for anonymous users
Thanks to Svetla Yankova for the best parts of the code.
Attached is the complete C# file: SearchResultsByPermissions
This blog post will guide you through the steps on how to trim the search results based on permissions or roles. Let's start with the use case:
I've got a page which is visible only to authenticated users:
Figure: This page is visible only to Authenticated users.
So, if you are not logged in in the front end you will not see the page in the navigation. Even if you manually type its URL in the address bar you will be first redirected to the login screen. So far so good.
Imagine now that an anonymous user performs a search that returns this particular page as a result. He would be able to see it in the search results and maybe even see some confidential information:
Figure: an Anonymous user performs a search which returns the page he's not supposed to see
Note: the unauthenticated user will not be able to open the page, but still be able to see its title, URL and some of the content.
The steps below will show you how to change the behaviour of the SearchResults widget so that it takes into account the permissions of the individual result items.
1. Create a class in your Visual Studio solution that inherit from the SearchResults class:
public
class
SearchResultsByPermissions : SearchResults
{
}
2. Override the InitializeControls method:
public
class
SearchResultsByPermissions : SearchResults
{
protected
override
void
InitializeControls(GenericContainer container)
{
Label resultsStats =
this
.ResultsStats;
//this is the original localized stats message. It shows all the results
var resultsStatsMessage = resultsStats.Text;
base
.InitializeControls(container);
if
(
string
.IsNullOrEmpty(
this
.Query))
{
this
.ResultsStats.Text =
string
.Empty;
return
;
}
int
numberOfAllResults = 0;
SearchResults.ISearcher searcher =
this
.GetSearcher();
//these are all the results (not filtered by permissions)
var allResults = searcher.Search(
this
.Query,
this
.IndexCatalogue, 0, 0,
out
numberOfAllResults);
if
(allResults ==
null
)
{
return
;
}
//here we will store only the results we have permissions to see
List<IDocument> securedResultSet =
new
List<IDocument>();
foreach
(var document
in
allResults)
{
var type = document.GetValue(
"ContentType"
);
var ID =
new
Guid(document.GetValue(
"OriginalItemId"
));
if
(TypeResolutionService.ResolveType(type) ==
typeof
(PageNode))
{
var manager = PageManager.GetManager();
//suppress the security checks so the code can be executed even if
//the current user doesn't have enough permissions
manager.Provider.SuppressSecurityChecks =
true
;
var page = manager.GetPageNode(ID);
if
(page !=
null
)
{
ISecuredObject securedObject = (ISecuredObject)page;
if
(SecurityExtensions.IsSecurityActionTypeGranted(securedObject, SecurityActionTypes.View))
{
securedResultSet.Add(document);
}
}
manager.Provider.SuppressSecurityChecks =
false
;
}
}
var numberOfSecuredSearchResults = securedResultSet.Count;
char
[] chrArray =
new
char
[] {
'\"'
};
string
str =
this
.Query.Trim(chrArray);
resultsStats.Text =
string
.Format(resultsStatsMessage, numberOfSecuredSearchResults, HttpUtility.HtmlEncode(str));
this
.ConfigurePager(numberOfSecuredSearchResults);
this
.ResultsList.DataSource =
null
;
int
itemsToSkip =
this
.GetItemsToSkip();
int
itemsToTake =
this
.GetItemsToTake();
ResultsList.DataSource = securedResultSet.Skip(itemsToSkip).Take(itemsToTake);
}
private
int
GetItemsToSkip()
{
if
(
this
.AllowPaging)
{
int
pageNumber =
this
.GetPageNumber(
this
.GetUrlEvaluationMode(),
this
.PageKey, 0,
"PageNumber"
);
if
(pageNumber > 0)
{
return
(pageNumber - 1) *
this
.ItemsPerPage;
}
}
return
0;
}
private
int
GetItemsToTake()
{
if
(!
this
.AllowPaging)
{
return
0;
}
return
this
.ItemsPerPage;
}
}
Here is some explanation: we call the base InitializeControls method because it takes care of calling the search service and binding the results to the ResultsList repeater. The ResultsList repeater is part of the UI that shows the results.
Remember, this results collection is not yet trimmed by permissions, so we need to alter it and include only the items that the current user is allowed to see.
To do that, we iterate through the collection and for each item we check its type. Based on the content type we invoke the appropriate manager and check if the View permission type is granted to the current user. If it is, then we add it to our securedResultSet collection.
At the end we bind the ResultsList repeater to the securedResultSet collection.
Note, you can use the same logic to perform checks on other content types like News, Documents, etc.
Finally, the message that shows the number of the returned results is updated, because the securedResultSet collection might contain less items than the original results collection.
3. Build and Register the new widget using Thunder in the page's toolbox.
4. Drag the new widget to a page and start using it.
That's it. Now the anonymous users will not find the pages they are not supposed to see:
Figure: the search results do not include the page that is hidden for anonymous users
Thanks to Svetla Yankova for the best parts of the code.
Attached is the complete C# file: SearchResultsByPermissions