Trim the Search Results based on permissions / roles

Trim the Search Results based on permissions / roles

Posted on March 18, 2013 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

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:
authenticatedUsers
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
progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation