I have a Sitecore v8.1 site using Solr 4.10. Here is my index setup. First I have two custom fields defined in my index defined as follows:
<field fieldName="read_roles" returnType="stringCollection">MyAssembly.ReadItemRoles,MyAssembly</field>
<field fieldName="denied_roles" returnType="stringCollection">MyAssembly.DenyReadItemRoles,MyAssembly</field>
The code for ReadItemRoles looks like this:
public class ReadItemRoles : IComputedIndexField
{
public object ComputeFieldValue(IIndexable indexable)
{
var scIndexable = indexable as SitecoreIndexableItem;
var item = (Item)scIndexable;
List<string> rolesList = new List<string>();
using (new Sitecore.SecurityModel.SecurityEnabler())
{
var roles = RolesInRolesManager.GetAllRoles();
var readRoles = roles.Where(r => item.Security.CanRead(r));
if (readRoles != null && readRoles.Any())
{
rolesList = readRoles.Select(r => r.Name.Replace(@"\", "|")).ToList();
}
}
return rolesList;
}
public string FieldName { get; set; }
public string ReturnType { get; set; }
}
The code for DenyReadItemRoles is similar and looks like this:
public class DenyReadItemRoles : IComputedIndexField
{
public object ComputeFieldValue(IIndexable indexable)
{
var scIndexable = indexable as SitecoreIndexableItem;
var item = (Item)scIndexable;
List<string> rolesList = new List<string>();
using (new Sitecore.SecurityModel.SecurityEnabler())
{
var roles = RolesInRolesManager.GetAllRoles();
var denyRoles = roles.Where(r => r.IsDenied(item));
if (denyRoles != null && denyRoles.Any())
{
rolesList = denyRoles.Select(r => r.Name.Replace(@"\", "|")).ToList();
}
}
if (rolesList.Count == 0)
{
var denyRoles = new List<string>();
denyRoles.Add("none");
return denyRoles;
}
return rolesList;
}
public string FieldName { get; set; }
public string ReturnType { get; set; }
}
internal static class SecurityExtensions
{
internal static bool IsDenied(this Role role, Item item)
{
if (item.Security.CanRead(role)) return false;
AccessRuleCollection accessRules = item.Security.GetAccessRules();
if (accessRules != null)
{
foreach (AccessRule rule in accessRules)
{
if (rule.SecurityPermission == SecurityPermission.DenyAccess &&
rule.AccessRight == AccessRight.ItemRead && rule.Account == role)
{
return true;
}
}
}
return (item.Parent == null) ? false : role.IsDenied(item.Parent);
}
}
So the intent here is that after I index my web database I should have a field for every item called read_roles that has a list of all of the roles that can read that item (with a | character instead of any backslashes). And I should have a field called denied_roles that has a list of all of the roles that can't read that item. These lists of roles are stored in a field of type stringCollection in Solr.
Now I want to apply a security filter to every site search that we do. Basically I want to show all results where at least one of the current user's roles is in the read_roles field and none of the user's roles are in the denied_roles field. Here is my code to do that:
public static IQueryable<T> ApplySecurityFilter<T>(this IQueryable<T> query) where T : SearchResultItem {
var userRoles = Sitecore.Context.User.Roles.Select(r => r.Name.Replace(@"\", "|"));
var readPredicate = PredicateBuilder.False<T>();
readPredicate = userRoles.Aggregate(readPredicate, (current, role) => current.Or(i => i["read_roles"].Equals(role)));
var denyPredicate = PredicateBuilder.True<T>();
denyPredicate = userRoles.Aggregate(denyPredicate, (current, role) => current.And(i => !i["denied_roles"].Equals(role)));
if (readPredicate.Body.NodeType != System.Linq.Expressions.ExpressionType.Constant) {
query = query.Filter(readPredicate);
}
if (readPredicate.Body.NodeType != System.Linq.Expressions.ExpressionType.Constant) {
query = query.Filter(denyPredicate);
}
return query;
}
The logic here is that I am getting a list of all of the current user's roles and I am extracting out a list of all of the role names and I am replacing any backslashes with the | character. Then I am using the OR operator to compare each role name to see if any of them are in the read_roles field. And I am using the AND operator to make sure none of them are in the denied_roles field.
For some reason this isn't working and I am having a lot of trouble figuring out why it is failing. The best that I can figure out is that this code seems to be failing when the user belongs to a role that contains a space in it. And only the denied_roles part of the query is failing. I have been troubleshooting this by opening up the Sitecore Search.config file and looking at the query that was sent to Solr. If I manually issue the same query to Solr using the Solr UI I get zero results. Then if I remove the denied_roles part I get results. So I am thinking it has something to do with the denied_roles field. But I am really not 100% sure.
EDIT: Here is an example of a query that doesn't seem to be working. I got this from the Sitecore Search.log file.
?q=(-isstandardvalue_b:(True) AND _language:(en)) AND (_content:(*acrygen*) OR _name:(*acrygen*) OR (short_description_t:(*acrygen*))^5 OR (search_title_t:(*acrygen*))^5)&start=0&rows=10&fl=*,score
&fq=((((-denied_roles_sm:("extranet|Anonymous Role") AND -denied_roles_sm:("extranet|Approved Role")) AND (read_roles_sm:("extranet|Anonymous Role") OR read_roles_sm:("extranet|Approved Role"))) AND _latestversion:(1)))