6

I have a site that I have upgraded from v7.5 to v8.1. In addition I have changed from Solr 4.7 to Solr 4.10. In my general search routine I am trying to filter out content items based on Sitecore role membership and which roles have read access to an item and which roles are denied. To accomplish this we have two custom fields in the Solr index. One is called read_roles and one is called denied_roles. For the purposes of this question all that really matters is read_roles.

The read_roles field in the Solr index looks like this:

"read_roles_sm": [
          "Emailcampaign\\Common Opt Out",
          "Emailcampaign\\Two-Column Message Opted Out",
          "extranet\\Anonymous Role",
          "extranet\\Approved Role",
          "extranet\\DistribA",
          "extranet\\DistribB"
],

In my C# code that does the searching I have a method called ApplySecurityFilter that looks like this:

    public static IQueryable<T> ApplySecurityFilter<T>(this IQueryable<T> query) where T : SearchResultItem {
        var userRoles = Sitecore.Context.User.Roles.Select(r => r.Name);

        var readPredicate = PredicateBuilder.False<T>();
        readPredicate = userRoles.Aggregate(readPredicate, (current, role) => current.Or(i => i["read_roles"].Contains(role)));

        var denyPredicate = PredicateBuilder.True<T>();
        denyPredicate = userRoles.Aggregate(denyPredicate, (current, role) => current.And(i => !i["denied_roles"].Contains(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;
    }

This all works fine in v7.5 with Solr 4.7. The code runs and translates my Linq style query in to a query that Solr can handle. In the end when I look in the log files this is part of the query that gets sent to Solr.

&fq=(((-denied_roles_sm:(*extranet\\Anonymous\ Role*) AND read_roles_sm:(*extranet\\Anonymous\ Role*)) AND _latestversion:(1))

And that works great. Now when I run the exact same code in v8.1 with Solr 4.10 for some reason the query that gets sent to Solr is slightly different. It looks like this:

&fq=(((-denied_roles_sm:("\*extranet\\Anonymous\\ Role\*") AND read_roles_sm:("\*extranet\\Anonymous\\ Role\*")) AND _latestversion:(1))

As you can see for some reason the Solr provider seems to have modified the text of the role name that gets sent to Solr. And there are no matches and therefore I get no results returned. I can't figure out why the upgraded version of my site is sending the query to Solr in a slightly different way. Any ideas?

3
  • 2
    It looks like Sitecore is applying an escape character sequence to the asterisk, which is why your query is no longer matching - it is now performing a literal search with the asterisk included. That said, I'm not sure why this would have changed between versions. Oct 17, 2016 at 16:04
  • Yes, also it put quotes around the string in the second example. My guess is that the Solr provider DLLs are doing this. But just a guess. Oct 17, 2016 at 16:17
  • @CoreyBurnett Have you tried implementing the suggestions from my answer? Have they worked for you? Oct 18, 2016 at 13:57

1 Answer 1

10

Making wildcard queries work

I had exactly the same issue with a custom Lucene query. The fix was to use .MatchWildcard() instead of .Contains().

In your case, to prevent escaping in both instances, you'd need to replace

i["fieldName"].Contains(role)

with

// C# 6
i["fieldName"].MatchWildcard($"*{role}*")
// C# 5
i["fieldName"].MatchWildcard(string.Format("*{0}*", role))

A potentially better approach

I have doubts as to why you even need a wildcard query. With a wildcard query, by querying *extranet\\Foo* you will match extranet\\FooBar as well, which is probably not your intention.

I think you want to match the exact role names, so Equals() instead of a wildcard query would work faster and more precisely.

var readPredicate = userRoles.Aggregate(
    PredicateBuilder.False<T>(),
    (current, role) => current.Or(i => i["read_roles"].Equals(role)));

var denyPredicate = userRoles.Aggregate(
    PredicateBuilder.True<T>(),
    (current, role) => current.And(i => !i["denied_roles"].Equals(role)));
2
  • 1
    I completely agree it's better to use .Equals instead of wildcard search but also would like to add that it's better to ensure we are not searching over tokenized field in this particular case. If do a wildcard search or search over tokenized field theoretically we could get some false matches therefore exposing a security hole. Oct 18, 2016 at 8:09
  • @AlexanderMogilka Agreed, thank you. I have updated the answer. Nov 1, 2016 at 10:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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