7

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)))
  • Has any of my two suggestions helped you? – Dmytro Shevchenko Oct 31 '16 at 19:15
5

Tokenization

I think that this problem is caused by the fact that your role fields are currently tokenized. I know that making the fields UNTOKENIZED in your previous question didn't help, but please try once again with the following instructions.

First, locate you computed fields under <fields hint="raw:AddComputedIndexField"> and make sure they are untokenized. For example:

<fields hint="raw:AddComputedIndexField">
  <field fieldName="read_roles" indexType="UNTOKENIZED" returnType="stringCollection">MyAssembly.ReadItemRoles,MyAssembly</field>
  <field fieldName="denied_roles" indexType="UNTOKENIZED" returnType="stringCollection">MyAssembly.DenyReadItemRoles,MyAssembly</field>
</fields>

Then also add these fields to the <fieldNames hint="raw:AddFieldByFieldName"> section (which is located under the <fieldMap> node):

<fieldNames hint="raw:AddFieldByFieldName">
  <field fieldName="read_roles" indexType="UNTOKENIZED" returnType="stringCollection" />
  <field fieldName="denied_roles" indexType="UNTOKENIZED" returnType="stringCollection" />
</fieldNames>

Rebuild your index and see if the queries work well now.

Sanitization

If the above doesn't help, then try replacing all non-alphanumeric characters with underscores and converting role names to lower case. This is sort of a hack, but at least it will make sure that there are no problems with tokenization and casing.

Put this method into a helper class and use it both in your computed fields and in the search query:

private static Regex _regex = new Regex("[^a-z0-9]+");

public static string SanitizeRoleName(string roleName)
{
  return _regex.Replace(roleName.ToLower(), "_");
}
  • Hi Dmytro, index rebuild is required if you add indexType="untokenized" ? – Arbejdsglæde Dec 21 '16 at 16:24
  • @Arbejdsglæde yes – Dmytro Shevchenko Dec 21 '16 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.