During a recent project, we had the request to implement a “gated” search; when people were logged in, we wanted to show them things in search that they had permission to see, where as if they were an anonymous user viewing search they would only see content marked as read by Everyone. On top of this, the Sitecore instance was multi-site (approximately 120 different sites each with their own Security Domain).

We fell back to what we knew best: a Computed search field. As you can see below, we are taking the item that is currently being indexed and looking up which roles CanRead from it:

The Computed Field

public class ReadAccessComputedField : IComputedIndexField
{
    public string FieldName { get; set; }
    public string ReturnType { get; set; }
    public object ComputeFieldValue(IIndexable indexable)
    {
        var sitecoreIndexable = indexable as SitecoreIndexableItem;
        if (sitecoreIndexable == null) return null;
        var item = (Item)sitecoreIndexable;
        var rolesForRead = new List<string>();

        // only indexing content security roles and anonymous at the moment
        var roles = new List<string>();
        roles.Add("Everyone");
        roles.Add("default\\Anonymous");
        roles.Add(string.Format("{0}\\Anonymous", Sitecore.Context.Domain));
        var contentSecurityRoles = _getContentSecurityRolesMappings().Select(x => x.Role).ToList();
        roles.AddRange(contentSecurityRoles);

        var targetSite = Sitecore.Links.LinkManager.ResolveTargetSite(item);
        using (new SiteContextSwitcher(Factory.GetSite(targetSite.Name)))
        {
            foreach (var role in roles)
            {
                var currentRole = Role.FromName(role);
                using (new SecurityEnabler())
                {
                    var canRead = item.Security.CanRead(currentRole);
                    if (canRead)
                    {
                        rolesForRead.Add(currentRole.Name);
                    }
                }
            }
            return rolesForRead;
        }
    }

    /// <summary>
    /// Gets an explicit list of roles that are applied to content 
    /// to "gate" it from anonymous users rather than trying to iterate
    /// thru every role in Sitecore
    /// </summary>
    /// <returns>List&lt;ContentSecurityRole&gt;</returns>
    public static List<ContentSecurityRole> _getContentSecurityRolesMappings()
    {
        var lst = new List<ContentSecurityRole>();
    
        // Read the configuration nodes
        foreach (XmlNode node in Factory.GetConfigNodes("yourSite.Account/contentSecurityRole"))
        {
            //Create a element of this type
            var elem = new ContentSecurityRole();
            elem.Name = XmlUtil.GetAttribute("name", node);
            elem.Role = XmlUtil.GetAttribute("value", node);
            lst.Add(elem);
        }

        return lst;
    }
}

As you can see above, the _getContentSecurityRolesMappings gets a very explicit list of Roles that we are actually using to restrict the content. This was significantly faster than iterating thru every role in Sitecore per item. That explicit mapping took place in a separate config:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <yourSite.Account>
      <contentSecurityRole name="Pizza Group 1" value="yoursite_domain\Pizza_Role" />
      <contentSecurityRole name="Cake Group 1" value="yoursite2_domain\Cake_Role" />
    </yourSite.Account>
  </sitecore>
</configuration>

The above xml mapped into the following class within the _getContentSecurityRolesMappings method:

public class CommunityMapping
{
    public string Name { get; set; }
    public string Role { get; set; }
}

The Index Field

Add the needed IndexField to your SearchResultItem:

public class YourSearchResultItem : SearchResultItem
{
    //other fields on your search item
    [IndexField("_read_access")]
    public IEnumerable<string> ReadAccess { get; set; }
}

And get the ComputedField running on index:

<fields hint="raw:AddComputedIndexField">
    <!-- other computed fields -->
    <field fieldName="_read_access" returnType="stringCollection">YourSite.Search.ComputedFields.ReadAccessComputedField, YourSite</field>
</fields>

Ok, so at this point we have a new field with all the applicable Read roles stored in it, the field to get us access to it from the ContentSearch API, now we need to filter our content by this role (the easy part). I personally love the PredicateBuilder so I will use that as an example, but you should be able to massage this into whatever approach you are using as well:

The Filter

//deep in the bowels of a search service...

var mainPredicate = PredicateBuilder.True<YourSearchResultItem>();

// other predicates like content type filtering, path filtering here....

var accessPredicate = PredicateBuilder.False<YourSearchResultItem>();
var currentUserRoles = Sitecore.Context.User.Roles.Select(x => x.Name);
accessPredicate = accessPredicate.Or(a => a.ReadAccess.Contains("Everyone"));
accessPredicate = accessPredicate.Or(a => a.ReadAccess.Contains("default\\Anonymous"));
accessPredicate = accessPredicate.Or(a => a.ReadAccess.Contains(string.Format("{0}\\Anonymous", Sitecore.Context.Domain)));

foreach (var currentUserRole in currentUserRoles)
{
    accessPredicate = accessPredicate.Or(a => a.ReadAccess.Contains(currentUserRole));
}

mainPredicate = predicate.And(accessPredicate);
// get results, paging, you know the drill

So the above will create an Or filter for any of the roles that will likely apply to the current user (anonymous or any current roles I may be logged in to) and pass that along to filter the content that applies to that set of roles.

And that my friends is what we did to satisfy the need for gated content in search within a multi-site environment.

Hope this helps!