sentry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kal...@apache.org
Subject [sentry] branch master updated: SENTRY-2496: Support multi-field attribute based document level controls for Solr. (Tristan Stevens reviewed by Hrishikesh Gadre and Kalyan Kumar Kalvagadda)
Date Tue, 26 Feb 2019 15:04:01 GMT
This is an automated email from the ASF dual-hosted git repository.

kalyan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sentry.git


The following commit(s) were added to refs/heads/master by this push:
     new 9820281  SENTRY-2496: Support multi-field attribute based document level controls for Solr. (Tristan Stevens reviewed by Hrishikesh Gadre and Kalyan Kumar Kalvagadda)
9820281 is described below

commit 9820281569d0da85db58c8de06a18a99efd09b8b
Author: Kalyan Kumar Kalvagadda <kkalyan@cloudera.com>
AuthorDate: Mon Feb 25 12:10:47 2019 -0600

    SENTRY-2496: Support multi-field attribute based document level controls for Solr. (Tristan Stevens reviewed by Hrishikesh Gadre and Kalyan Kumar Kalvagadda)
    
    Change-Id: Id4b3ebc7175634b915ec49f08783c9d83e9da270
---
 sentry-solr/solr-sentry-handlers/pom.xml           |   28 +-
 .../component/CachingUserAttributeSource.java      |   91 ++
 .../handler/component/FieldToAttributeMapping.java |  103 ++
 .../handler/component/LdapUserAttributeSource.java |  283 ++++
 .../component/LdapUserAttributeSourceParams.java   |  227 +++
 .../handler/component/SolrAttrBasedFilter.java     |  325 ++++
 .../handler/component/UserAttributeSource.java     |   45 +
 .../component/UserAttributeSourceParams.java       |   31 +
 .../component/CachingUserAttributeSourceTest.java  |  154 ++
 .../solr/handler/component/LdapRegexTest.java      |  130 ++
 .../handler/component/MockUserAttributeSource.java |   55 +
 sentry-tests/sentry-tests-solr/pom.xml             |    6 +
 .../tests/e2e/solr/SolrSentryServiceTestBase.java  |   27 +
 .../sentry/tests/e2e/solr/TestAbacOperations.java  |  465 ++++++
 .../src/test/resources/ldap/ldap.ldiff             |  342 ++++
 .../src/test/resources/ldap/ldap.schema            | 1692 ++++++++++++++++++++
 .../cloud-minimal_abac/conf/enumsConfig.xml        |   39 +
 .../configsets/cloud-minimal_abac/conf/schema.xml  |   40 +
 .../cloud-minimal_abac/conf/solrconfig.xml         |  112 ++
 19 files changed, 4194 insertions(+), 1 deletion(-)

diff --git a/sentry-solr/solr-sentry-handlers/pom.xml b/sentry-solr/solr-sentry-handlers/pom.xml
index 621d832..2b529e2 100644
--- a/sentry-solr/solr-sentry-handlers/pom.xml
+++ b/sentry-solr/solr-sentry-handlers/pom.xml
@@ -101,6 +101,32 @@ limitations under the License.
       <artifactId>cglib-nodep</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>${mockito.version}</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
-
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <reuseForks>false</reuseForks>
+          <!-- Currently SOLR E2E tests don't work if restarted after the failure -->
+          <rerunFailingTestsCount>0</rerunFailingTestsCount>
+          <systemPropertyVariables>
+            <test.solr.allowed.securerandom>NativePRNG</test.solr.allowed.securerandom>
+            <!-- Solr test framework randomizes the locale configuration which sometimes
+                 result in test failures due to derby initialization errors (Ref: LUCENE-8009).
+                 To reduce the flakiness of the unit tests, setting the locale explicitly.
+             -->
+            <tests.locale>en-US</tests.locale>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
 </project>
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/CachingUserAttributeSource.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/CachingUserAttributeSource.java
new file mode 100644
index 0000000..9f3fc34
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/CachingUserAttributeSource.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Ticker;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Multimap;
+import org.apache.solr.common.SolrException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Decorator class for any other UserAttributeSource which implements a simple cache around the UserAttributeSource
+ * The cache avoids repeated calls to the UAS upon repeated Solr queries
+ */
+public class CachingUserAttributeSource implements UserAttributeSource {
+
+  private static final Logger LOG = LoggerFactory.getLogger(CachingUserAttributeSource.class);
+  private final LoadingCache<String, Multimap<String, String>> cache;
+
+  /**
+   * @param userAttributeSource {@link UserAttributeSource} being decorated
+   * @param ttlSeconds Time To Live (seconds) for the cache before entries will be aged
+   * @param maxCacheSize The maximum number of entries the cache should contain before entries are evicted
+   */
+  public CachingUserAttributeSource(final UserAttributeSource userAttributeSource, long ttlSeconds, long maxCacheSize) {
+    this(userAttributeSource, ttlSeconds, maxCacheSize, null);
+  }
+
+  /**
+   * @param userAttributeSource {@link UserAttributeSource} being decorated
+   * @param ttlSeconds Time To Live (seconds) for the cache before entries will be aged
+   * @param maxCacheSize The maximum number of entries the cache should contain before entries are evicted
+   * @param ticker A {@link Ticker} used for testing cache expiry. If null the system clock will be used
+   */
+  @VisibleForTesting
+  /* default */ CachingUserAttributeSource(final UserAttributeSource userAttributeSource, long ttlSeconds, long maxCacheSize, Ticker ticker) {
+    LOG.debug("Creating cached user attribute source, userAttributeSource={}, ttlSeconds={}, maxCacheSize={}", userAttributeSource, ttlSeconds, maxCacheSize);
+    CacheLoader<String, Multimap<String, String>> cacheLoader = new CacheLoader<String, Multimap<String, String>>() {
+      public Multimap<String, String> load(String userName) {
+        LOG.debug("User attribute cache miss for user: {}", userName);
+        return userAttributeSource.getAttributesForUser(userName);
+      }
+    };
+    CacheBuilder builder = CacheBuilder.newBuilder().expireAfterWrite(ttlSeconds, TimeUnit.SECONDS).maximumSize(maxCacheSize);
+    if (ticker != null) {
+      builder.ticker(ticker);
+    }
+    cache = builder.build(cacheLoader);
+  }
+
+  @Override
+  public Multimap<String, String> getAttributesForUser(String userName) {
+    try {
+      return cache.get(userName);
+    } catch (ExecutionException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          "Error getting user attributes from cache", e);
+    }
+  }
+
+  @Override
+  public Class<? extends UserAttributeSourceParams> getParamsClass() {
+    return null;
+  }
+
+  @Override
+  public void init(UserAttributeSourceParams params, Collection<String> attributes) {
+  }
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/FieldToAttributeMapping.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/FieldToAttributeMapping.java
new file mode 100644
index 0000000..00d3f28
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/FieldToAttributeMapping.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class used by {@link SolrAttrBasedFilter} to hold the configuration which maps fields in Solr to one or more
+ * LDAP attributes. Includes details such as whether empty values in the doc should be permitted, whether there is a
+ * default value for all users, whether any RegExs should be applied and any extra options that should be passed
+ * to the fq
+ */
+public class FieldToAttributeMapping {
+
+  private static final Splitter ATTR_NAME_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
+  private final String fieldName;
+  private final Collection<String> attributes;
+  private final FilterType filterType;
+  private final boolean acceptEmpty;
+  private final String allUsersValue;
+  private final Pattern attrValueRegex;
+  private final String extraOpts;
+
+  /**
+   * The four filter types currently supported, AND, OR, LessThanOrEqualTo (LTE), GreaterThanOrEqualTo (GTE)
+   * Expected to be expanded in the future
+   */
+  enum FilterType {
+    AND,
+    OR,
+    LTE,
+    GTE
+  }
+
+  /**
+   * @param fieldName The field being mapped
+   * @param ldapAttributeNames comma delimited list of attributes which will be used to acquire values for this field
+   * @param filterType filter type can be any one of {@link FilterType}
+   * @param acceptEmpty true if an empty value in the Solr field should be counted as a match (i.e. doc returned)
+   * @param allUsersValue the value which the field may contain that would indicate that all users should see this doc
+   * @param valueFilterRegex String representation of a {@link Pattern} that will be applied to attributes retrieved
+   *                         from the attribute source. Note: If match groups are used, the last non-null match-group
+   *                         will be applied as the value for this filter
+   * @param extraOpts Any extra options that should be passed to the filter as constructed before appending to the fq
+   */
+  public FieldToAttributeMapping(String fieldName, String ldapAttributeNames, String filterType, boolean acceptEmpty, String allUsersValue, String valueFilterRegex, String extraOpts) {
+    this.fieldName = fieldName;
+    this.attributes = Collections.unmodifiableSet(Sets.newHashSet(ATTR_NAME_SPLITTER.split(ldapAttributeNames)));
+    this.filterType = FilterType.valueOf(filterType);
+    this.acceptEmpty = acceptEmpty;
+    this.allUsersValue = allUsersValue;
+    this.attrValueRegex = Pattern.compile(valueFilterRegex);
+    this.extraOpts = extraOpts;
+  }
+
+  public String getFieldName() {
+    return fieldName;
+  }
+
+  public Collection<String> getAttributes() {
+    return attributes;
+  }
+
+  public FilterType getFilterType() {
+    return filterType;
+  }
+
+  public boolean getAcceptEmpty() {
+    return acceptEmpty;
+  }
+
+  public String getAllUsersValue() {
+    return allUsersValue;
+  }
+
+  public Pattern getAttrValueRegex() {
+    return attrValueRegex;
+  }
+
+  public String getExtraOpts() {
+    return extraOpts;
+  }
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/LdapUserAttributeSource.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/LdapUserAttributeSource.java
new file mode 100644
index 0000000..4101ec8
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/LdapUserAttributeSource.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.StartTlsRequest;
+import javax.naming.ldap.StartTlsResponse;
+import javax.net.ssl.HostnameVerifier;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static javax.naming.Context.INITIAL_CONTEXT_FACTORY;
+import static javax.naming.Context.PROVIDER_URL;
+import static javax.naming.Context.SECURITY_AUTHENTICATION;
+import static javax.naming.Context.SECURITY_CREDENTIALS;
+import static javax.naming.Context.SECURITY_PRINCIPAL;
+
+/**
+ * This class implements the logic to extract attributes for a specified user
+ * from LDAP server. Currently this class supports following modes of authentication
+ * with the LDAP server,
+ * - none (anonymous bind operation)
+ * - simple (based on username and password).
+ * - GSSAPI (kerberos)
+ *
+ * This filter accepts following parameters for interacting with LDAP server,
+ * ldapProviderUrl : Url of the LDAP server (e.g. ldap://myserver)
+ * ldapAuthType : Type of the authentication mechanism used to connect to LDAP server.
+ *                Currently supported values: simple or none
+ * ldapAdminUser : DN of the LDAP admin user (only applicable in case of "simple" authentication)
+ * ldapAdminPassword : password of the LDAP admin user (only applicable in case of "simple" authentication)
+ * ldapBaseDN : Base DN string to be used to query LDAP server. The implementation
+ *              prepends user name (i.e. uid=<user_name>) to this value for querying the
+ *              attributes.
+ *
+ * This class supports extracting single (or multi-valued) String attributes only. The
+ * raw value(s) of the attribute are used for document-level filtering.
+ */
+public class LdapUserAttributeSource implements UserAttributeSource {
+
+  private static final HostnameVerifier PERMISSIVE_HOSTNAME_VERIFIER = (hostname, session) -> true;
+  private static final Logger LOG = LoggerFactory.getLogger(LdapUserAttributeSource.class);
+
+  /**
+   * Singleton cache provides the parent group(s) for a given group.
+   * The cache classes are threadsafe so can be shared by multiple LdapUserAttributeSource instances.
+   */
+  private static volatile Cache<String, Set<String>> scache;
+  private static final Object SCACHE_SYNC = new Object();
+
+  public static Cache<String, Set<String>> getCache(long ttlSeconds, long maxCacheSize) {
+    if (scache != null) {
+      return scache;
+    }
+    synchronized (SCACHE_SYNC) {
+      if (scache == null) {
+        LOG.info("Creating access group cache, ttl={} maxSize={}", ttlSeconds, maxCacheSize);
+        scache = CacheBuilder.newBuilder().expireAfterWrite(ttlSeconds, TimeUnit.SECONDS).maximumSize(maxCacheSize).build(); // No auto-load in case of a cache miss - must populate explicitly
+      }
+      return scache;
+    }
+  }
+
+  @SuppressWarnings({"rawtypes", "PMD.ReplaceHashtableWithMap"})
+  private Hashtable env;
+  private LdapUserAttributeSourceParams params;
+  private SearchControls searchControls;
+
+  // Per-instance copy of the static singleton cache
+  private Cache<String, Set<String>> cache;
+
+  public void init(UserAttributeSourceParams params, Collection<String> attributes) {
+    LOG.debug("Creating LDAP user attribute source, params={}, attributes={}", params, attributes);
+
+    if (!(params instanceof LdapUserAttributeSourceParams)) {
+      throw new SolrException(ErrorCode.INVALID_STATE, "LdapUserAttributeSource has been misconfigured with the wrong parameters {" + params.getClass().getName() + "}");
+    }
+
+    this.params = (LdapUserAttributeSourceParams) params;
+    this.env = toEnv(this.params);
+
+    searchControls = new SearchControls();
+    searchControls.setReturningAttributes(attributes.toArray(new String[attributes.size()]));
+    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+    if (this.params.isStartTlsEnabled() && this.params.getServerUrl().startsWith("ldaps://")) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Start TLS should not be used with ldaps://");
+    }
+
+    long cacheTtl = this.params.getGroupCacheTtl();
+    long cacheMaxSize = this.params.getGroupCacheMaxSize();
+    cache = getCache(cacheTtl, cacheMaxSize); // Singleton; only the first spec seen will be used
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked", "PMD.ReplaceHashtableWithMap"})
+  private Hashtable toEnv(LdapUserAttributeSourceParams params) {
+    final Hashtable result = new Hashtable();
+    result.put(INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+    result.put(PROVIDER_URL, params.getServerUrl());
+    String authType = params.getAuthType();
+    result.put(SECURITY_AUTHENTICATION, authType);
+    if ("simple".equals(authType)) {
+      result.put(SECURITY_PRINCIPAL, params.getUsername());
+      result.put(SECURITY_CREDENTIALS, params.getPassword());
+    }
+    return result;
+  }
+
+  /**
+   * Return specified LDAP attributes for the user identified by <code>userName</code>
+   */
+  @Override
+  @SuppressWarnings({"rawtypes", "unchecked", "PMD.ReplaceHashtableWithMap"})
+  public Multimap<String, String> getAttributesForUser(String userName) {
+    LdapContext ctx = null;
+    try {
+      ctx = new InitialLdapContext(env, null);
+      Multimap<String, String> result;
+      if (params.isStartTlsEnabled()) {
+        StartTlsResponse tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
+        if (params.isHostNameVerificationDisabled()) {
+          tls.setHostnameVerifier(PERMISSIVE_HOSTNAME_VERIFIER);
+        }
+        tls.negotiate();
+        result = doAttributeSearch(userName, ctx);
+        tls.close();
+      } else {
+        result = doAttributeSearch(userName, ctx);
+      }
+      return result;
+    } catch (NamingException | IOException e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Unable to query LDAP server", e);
+    } finally {
+      if (ctx != null) {
+        try {
+          ctx.close();
+        } catch (NamingException ignored) {
+        }
+      }
+    }
+  }
+
+  @Override
+  public Class<LdapUserAttributeSourceParams> getParamsClass() {
+    return LdapUserAttributeSourceParams.class;
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private Multimap<String, String> doAttributeSearch(String userName, LdapContext ctx) throws NamingException {
+    NamingEnumeration searchResults = ctx.search(params.getBaseDn(), params.getUserFilter().replace("{0}", userName), searchControls);
+    if (!searchResults.hasMore()) {
+      LOG.error("User '{}' not found in LDAP", userName);
+      throw new SolrException(ErrorCode.SERVER_ERROR, "User not found in LDAP");
+    }
+    LOG.info("Fetching attributes for {} from LDAP using {}", userName, this);
+    Multimap<String, String> result = LinkedHashMultimap.create(); // NB LinkedHashMultimap does not allow duplicate values.
+    while (searchResults.hasMore()) {
+      SearchResult entry = (SearchResult) searchResults.next();
+      Attributes attributes = entry.getAttributes();
+      NamingEnumeration<? extends Attribute> attrEnum = attributes.getAll();
+      while (attrEnum.hasMore()) {
+        Attribute a = attrEnum.next();
+        NamingEnumeration values = a.getAll();
+        while (values.hasMore()) {
+          result.put(a.getID(), (String) values.next());
+        }
+      }
+    }
+    LOG.debug("Direct attributes found for user {}: {}", userName, result);
+
+    // Optionally, recurse along the specified property such as "memberOf" to find indirect (nested) group memberships.
+    // A maxDepth of 1 indicates that we find direct groups and parent groups. A maxDepth of 2 also includes grandparents, etc.
+    if (params.isNestedQueryEnabled() && params.getMaxRecurseDepth() > 0) {
+      LOG.debug("Querying nested groups for user {} up to depth {}", userName, params.getMaxRecurseDepth());
+      String recursiveAttr = params.getRecursiveAttribute(); // Configurable, but typically "memberOf"
+      // Important: take a defensive copy of the original groups, because we modify the Multimap inside the loop:
+      Set<String> values = new HashSet(result.get(recursiveAttr)); //  Multimap.get() is never null
+      for (String group : values) {
+          Set<String> known = new HashSet<>();
+          known.add(group); // avoid cycles to self.
+          getParentGroups(group, known, ctx, 1); // modifies the 'known' Set, adding any new groups found
+          known.remove(group); // We already have this group in the result, or we wouldn't be here. Remove for clarity of logging:
+          LOG.debug("Adding parent groups for {} : {}", group, known);
+          result.putAll(recursiveAttr, known);
+      }
+      LOG.debug("Total attributes found for user {}: {}", userName, result);
+    }
+    return result;
+  }
+
+  /**
+   * Recursively find the parent groups (if any) of the specified group, and add them to the user's direct attribute,
+   * so that we can return both direct and indirect group memberships. Limit the depth of recursion. Detect and avoid loops.
+   *
+   * As a special case, caches the top-level "ancestor" groups (which have no parent groups) to reduce the number of LDAP queries.
+   * Does not cache other groups because, in general, the cycle-detection may terminate recursion and prevent us from getting
+   * and thus caching a full picture of the groups reachable from a given group. It would also be possible to cache the bottom-level
+   * groups in the main loop in doAttributeSearch, but this is not implemented at present.
+   */
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private Set<String> getParentGroups(String childGroup, Set<String> knownGroups, LdapContext ctx, int depth) throws NamingException {
+    // Throw an exception if we have exceeded the recursion depth; this causes the entire query to fail.
+    // This alerts us that the recursion depth is insufficient to traverse all of the user's nested groups
+    // and will need increasing (or the group structure will need to be simplified).
+    if (depth > params.getMaxRecurseDepth()) {
+      // E.g. maxRecurseDepth=1 means that only direct groups are allowed; no parent groups can be handled
+      // maxRecurseDepth=3 means that direct groups, parent, and grandparent groups can be handled
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Nested groups recursion limit exceeded for group " + childGroup + " at depth " + depth);
+    }
+
+    // First try the cache:
+    Set<String> parents = cache.getIfPresent(childGroup); // nullable
+    if (parents != null) {
+      LOG.debug("Cache hit for {} : {}", childGroup, parents); // Currently we only cache "ancestor" groups, which have NO parents!
+      knownGroups.addAll(parents); // Added since 0.0.6-SNAPSHOT delivered to site; important fix for when we start caching non-ancestor groups
+      return parents; // cache hit
+    } else { // Cache miss
+     LOG.debug("Querying LDAP for parent groups of {} at depth {}...", childGroup, depth);
+      String recursiveAttr = params.getRecursiveAttribute(); // Configurable, but typically "memberOf"
+
+      // There may be potential optimisations here if we can skip queries for some groups
+      // depending on parts of their CN, e.g. for CN=Builtin groups *if* they are known to be top-level groups.
+
+      Attributes atts = ctx.getAttributes(childGroup, new String[] {recursiveAttr}); // attributes such as memberOf will return the DN; the regex in solrconfig.xml will need to extract the CN from the DN.
+      Attribute att = atts.get(recursiveAttr);
+      if (att != null && att.size() > 0) {
+        LOG.debug("Group {} has direct parent groups: {}", childGroup, att);
+        NamingEnumeration<?> parentGroups = att.getAll();
+        while (parentGroups.hasMore()) {
+          String parentGroup = parentGroups.next().toString();
+          // Skip recursion if we've seen this group before; avoid cycles and multiple paths through same ancestors
+          if (knownGroups.add(parentGroup)) {
+            LOG.debug("Found new parent group: {} - recursing...", parentGroup);
+            // Recurse until we find a group that has no parents, or we hit the depth limit (which throws an Exception above)
+            getParentGroups(parentGroup, knownGroups, ctx, depth + 1);
+            // Don't cache these results! They may be incomplete because cycle detection will stop recursion early.
+          } else {
+            LOG.debug("Cycle detected for parent group: {} - stopping recursion.", parentGroup);
+          }
+        }
+      } else {
+        LOG.debug("No parent groups found for group {}", childGroup);
+        cache.put(childGroup, Collections.emptySet()); // This is an "ancestor" group with no parents
+      }
+      return knownGroups;
+    }
+  }
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/LdapUserAttributeSourceParams.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/LdapUserAttributeSourceParams.java
new file mode 100644
index 0000000..f7246af
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/LdapUserAttributeSourceParams.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import org.apache.solr.common.params.SolrParams;
+
+public class LdapUserAttributeSourceParams implements UserAttributeSourceParams {
+
+  private String baseDn;
+  private String userFilter;
+  private String serverUrl;
+  private String authType;
+  private String username;
+  private String password;
+  private boolean enableStartTls;
+  private boolean disableHostNameVerification;
+
+  private boolean doNestedQuery;     // Whether to recursively follow recursiveAttribute to find parent groups
+  private String recursiveAttribute; // Only required if doNestedQuery==true
+  private int maxRecurseDepth;       // Only used if doNestedQuery==true
+  private long groupCacheTtl;
+  private long groupCacheMaxSize;
+
+  public static final String LDAP_ADMIN_USER = "ldapAdminUser";
+  public static final String LDAP_ADMIN_PASSWORD = "ldapAdminPassword";
+  public static final String LDAP_AUTH_TYPE = "ldapAuthType";
+  public static final String LDAP_AUTH_TYPE_DEFAULT = "none";
+  public static final String LDAP_BASE_DN = "ldapBaseDN";
+  public static final String LDAP_TLS_ENABLED = "ldapTlsEnabled";
+  public static final boolean LDAP_TLS_ENABLED_DEFAULT = false;
+  public static final String LDAP_TLS_DISABLE_HOSTNAME_VERIFICATION = "ldapTlsDisableHostnameVerification";
+  public static final boolean LDAP_TLS_DISABLE_HOSTNAME_VERIFICATION_DEFAULT = false;
+  public static final String LDAP_USER_SEARCH_FILTER = "ldapUserSearchFilter";
+  public static final String LDAP_USER_SEARCH_FILTER_DEFAULT = "(uid={0})";
+  public static final String LDAP_PROVIDER_URL = "ldapProviderUrl";
+
+  // Properties for handling nested access groups recursively
+  public static final String LDAP_NESTED_GROUPS_ENABLED = "ldapNestedGroupsEnabled";
+  public static final boolean LDAP_NESTED_GROUPS_ENABLED_DEFAULT = false; // Disabled by default for performance reasons
+  public static final String LDAP_RECURSIVE_ATTRIBUTE = "ldapRecursiveAttribute";
+  public static final String LDAP_RECURSIVE_ATTRIBUTE_DEFAULT = "memberOf";
+  public static final String LDAP_MAX_RECURSE_DEPTH = "ldapMaxRecurseDepth";
+  public static final int LDAP_MAX_RECURSE_DEPTH_DEFAULT = 5;
+
+  // Caching of access groups to reduce number of LDAP queries for nested groups
+  public static final String LDAP_GROUP_CACHE_TTL_SECONDS = "ldapGroupCacheTtlSeconds";
+  public static final long LDAP_GROUP_CACHE_TTL_SECONDS_DEFAULT = 30;
+  public static final String LDAP_GROUP_CACHE_MAX_SIZE = "ldapGroupCacheMaxSize";
+  public static final long LDAP_GROUP_CACHE_MAX_SIZE_DEFAULT = 1000;
+
+  public String getAuthType() {
+    return authType;
+  }
+
+  public void setAuthType(String authType) {
+    Preconditions.checkNotNull(authType);
+    this.authType = authType;
+  }
+
+  public String getBaseDn() {
+    return baseDn;
+  }
+
+  public void setBaseDn(String baseDn) {
+    Preconditions.checkNotNull(baseDn);
+    this.baseDn = baseDn;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  public String getServerUrl() {
+    return serverUrl;
+  }
+
+  public void setServerUrl(String serverUrl) {
+    Preconditions.checkNotNull(serverUrl);
+    this.serverUrl = serverUrl;
+  }
+
+  public String getUserFilter() {
+    return userFilter;
+  }
+
+  public void setUserFilter(String userFilter) {
+    Preconditions.checkNotNull(userFilter);
+    this.userFilter = userFilter;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public boolean isHostNameVerificationDisabled() {
+    return disableHostNameVerification;
+  }
+
+  public void setDisableHostNameVerification(boolean disableHostNameVerification) {
+    this.disableHostNameVerification = disableHostNameVerification;
+  }
+
+  public boolean isStartTlsEnabled() {
+    return enableStartTls;
+  }
+
+  public void setEnableStartTls(boolean enableStartTls) {
+    this.enableStartTls = enableStartTls;
+  }
+
+  public boolean isNestedQueryEnabled() {
+    return doNestedQuery;
+  }
+
+  public void setNestedQueryEnabled(boolean doNestedQuery) {
+    this.doNestedQuery = doNestedQuery;
+  }
+
+  public String getRecursiveAttribute() {
+    return recursiveAttribute;
+  }
+
+  public void setRecursiveAttribute(String attr) {
+    this.recursiveAttribute = attr;
+  }
+
+  public int getMaxRecurseDepth() {
+    return maxRecurseDepth;
+  }
+
+  public void setMaxRecurseDepth(int maxDepth) {
+    this.maxRecurseDepth = maxDepth;
+  }
+
+  public long getGroupCacheTtl() {
+    return groupCacheTtl;
+  }
+
+  public void setGroupCacheTtl(long groupCacheTtl) {
+    this.groupCacheTtl = groupCacheTtl;
+  }
+
+  public long getGroupCacheMaxSize() {
+    return groupCacheMaxSize;
+  }
+
+  public void setGroupCacheMaxSize(long groupCacheMaxSize) {
+    this.groupCacheMaxSize = groupCacheMaxSize;
+  }
+
+  // Note that equals(), hashCode() and toString() currently only use a subset of the attributes above - do they need extending?
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    LdapUserAttributeSourceParams that = (LdapUserAttributeSourceParams) o;
+    return Objects.equal(baseDn, that.baseDn) &&
+        Objects.equal(userFilter, that.userFilter) &&
+        Objects.equal(serverUrl, that.serverUrl) &&
+        Objects.equal(authType, that.authType) &&
+        Objects.equal(username, that.username) &&
+        Objects.equal(password, that.password);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(baseDn, userFilter, serverUrl, authType, username, password);
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this)
+        .add("authType", authType)
+        .add("baseDn", baseDn)
+        .add("userFilter", userFilter)
+        .add("serverUrl", serverUrl)
+        .add("username", username)
+        .add("password", "***")
+        .toString();
+  }
+
+  @Override
+  public void init(SolrParams solrParams) {
+    setServerUrl(solrParams.get(LDAP_PROVIDER_URL));
+    setBaseDn(solrParams.get(LDAP_BASE_DN));
+    setUserFilter(solrParams.get(LDAP_USER_SEARCH_FILTER, LDAP_USER_SEARCH_FILTER_DEFAULT));
+    setAuthType(solrParams.get(LDAP_AUTH_TYPE, LDAP_AUTH_TYPE_DEFAULT));
+    setUsername(solrParams.get(LDAP_ADMIN_USER));
+    setPassword(solrParams.get(LDAP_ADMIN_PASSWORD));
+    setEnableStartTls(solrParams.getBool(LDAP_TLS_ENABLED, LDAP_TLS_ENABLED_DEFAULT));
+    setDisableHostNameVerification(solrParams.getBool(LDAP_TLS_DISABLE_HOSTNAME_VERIFICATION, LDAP_TLS_DISABLE_HOSTNAME_VERIFICATION_DEFAULT));
+    setNestedQueryEnabled(solrParams.getBool(LDAP_NESTED_GROUPS_ENABLED, LDAP_NESTED_GROUPS_ENABLED_DEFAULT));
+    setRecursiveAttribute(solrParams.get(LDAP_RECURSIVE_ATTRIBUTE, LDAP_RECURSIVE_ATTRIBUTE_DEFAULT));
+    setMaxRecurseDepth(solrParams.getInt(LDAP_MAX_RECURSE_DEPTH, LDAP_MAX_RECURSE_DEPTH_DEFAULT));
+    setGroupCacheMaxSize(solrParams.getLong(LDAP_GROUP_CACHE_MAX_SIZE, LDAP_GROUP_CACHE_MAX_SIZE_DEFAULT));
+    setGroupCacheTtl(solrParams.getLong(LDAP_GROUP_CACHE_TTL_SECONDS, LDAP_GROUP_CACHE_TTL_SECONDS_DEFAULT));
+  }
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/SolrAttrBasedFilter.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/SolrAttrBasedFilter.java
new file mode 100644
index 0000000..2e660c2
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/SolrAttrBasedFilter.java
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Multimap;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This custom {@linkplain SearchComponent} is responsible to introduce
+ * a document level security filter based on values of user attributes associated
+ * with the authenticated user.
+ *
+ * This filter also configures a {@Linkplain UserAttributeSource} via parameters evaluated by
+ * a {@Linkplan UserAttributeSourceParams}. All UserAttributeSourceParams should be initialized here
+ * andQParser : Name of the registered QParser used for subset matching (required for AND type filter).
+ * field_attr_mappings - A list mapping Solr index field -> user attribute on which the document level
+ *                      security is to be implemented. For each mapping, we need to specify -
+ *                      - name of the field in the Solr index
+ *                      - A comma-separated string of user attribute name (and its aliases).
+ *                      - Type of filter to be applied. Currently we support OR and AND based filters.
+ */
+public class SolrAttrBasedFilter extends DocAuthorizationComponent {
+
+  private static final Logger LOG = LoggerFactory.getLogger(SolrAttrBasedFilter.class);
+
+  public static final String CACHE_ENABLED_PROP = "cache_enabled";
+  public static final boolean CACHE_ENABLED_DEFAULT = false;
+  public static final String CACHE_TTL_PROP = "cache_ttl_seconds";
+  public static final long CACHE_TTL_DEFAULT = 30;
+  public static final String CACHE_MAX_SIZE_PROP = "cache_max_size";
+  public static final long CACHE_MAX_SIZE_DEFAULT = 1000;
+  public static final String ENABLED_PROP = "enabled";
+  public static final String FIELD_ATTR_MAPPINGS = "field_attr_mappings";
+
+  public static final String USER_ATTRIBUTE_SOURCE_CLASSNAME = "userAttributeSource";
+  public static final String USER_ATTRIBUTE_SOURCE_CLASSNAME_DEFAULT = "org.apache.solr.handler.component.LdapUserAttributeSource";
+
+  public static final String FIELD_FILTER_TYPE = "filter_type";
+  public static final String ATTR_NAMES = "attr_names";
+  public static final String PERMIT_EMPTY_VALUES = "permit_empty";
+  public static final String ALL_USERS_VALUE = "all_users_value";
+  public static final String ATTRIBUTE_FILTER_REGEX = "value_filter_regex";
+  public static final String AND_OP_QPARSER = "andQParser";
+  public static final String EXTRA_OPTS = "extra_opts";
+
+  private List<FieldToAttributeMapping> fieldAttributeMappings = new LinkedList<>();
+
+  private String andQParserName;
+  private UserAttributeSource userAttributeSource;
+  private boolean enabled = false;
+
+@SuppressWarnings({"rawtypes"})
+  @Override
+  public void init(NamedList args) {
+    LOG.debug("Initializing {}", this.getClass().getSimpleName());
+
+    SolrParams solrParams = args.toSolrParams();
+    if (args.getBooleanArg(ENABLED_PROP) != null) {
+      this.enabled = args.getBooleanArg(ENABLED_PROP);
+    }
+
+    NamedList mappings = checkAndGet(args, FIELD_ATTR_MAPPINGS);
+
+    Iterator<Map.Entry<String, NamedList>> iter = mappings.iterator();
+    while (iter.hasNext()) {
+      Map.Entry<String, NamedList> entry = iter.next();
+      String solrFieldName  = entry.getKey();
+      String attributeNames = checkAndGet(entry.getValue(), ATTR_NAMES);
+      String filterType     = checkAndGet(entry.getValue(), FIELD_FILTER_TYPE);
+      boolean acceptEmpty = false;
+      if (entry.getValue().getBooleanArg(PERMIT_EMPTY_VALUES) != null) {
+        acceptEmpty = entry.getValue().getBooleanArg(PERMIT_EMPTY_VALUES);
+      }
+      String allUsersValue  = getWithDefault(entry.getValue(), ALL_USERS_VALUE, "");
+      String regex          = getWithDefault(entry.getValue(), ATTRIBUTE_FILTER_REGEX, "");
+      String extraOpts      = getWithDefault(entry.getValue(), EXTRA_OPTS, "");
+      FieldToAttributeMapping mapping = new FieldToAttributeMapping(solrFieldName, attributeNames, filterType, acceptEmpty, allUsersValue, regex, extraOpts);
+      fieldAttributeMappings.add(mapping);
+    }
+
+    if (this.userAttributeSource == null) {
+      if (solrParams.getBool(CACHE_ENABLED_PROP, CACHE_ENABLED_DEFAULT)) {
+        this.userAttributeSource = new CachingUserAttributeSource(buildUserAttributeSource(solrParams), solrParams.getLong(CACHE_TTL_PROP, CACHE_TTL_DEFAULT), solrParams.getLong(CACHE_MAX_SIZE_PROP, CACHE_MAX_SIZE_DEFAULT));
+      } else {
+        this.userAttributeSource = buildUserAttributeSource(solrParams);
+      }
+    }
+
+    this.andQParserName = this.<String>checkAndGet(args, AND_OP_QPARSER).trim();
+  }
+
+  private UserAttributeSource buildUserAttributeSource(SolrParams solrParams) {
+    List<String> combinedAttributes = new LinkedList<>();
+    for (FieldToAttributeMapping mapping: fieldAttributeMappings) {
+      combinedAttributes.addAll(mapping.getAttributes());
+    }
+
+    String userAttributeSourceClassname = solrParams.get(USER_ATTRIBUTE_SOURCE_CLASSNAME, USER_ATTRIBUTE_SOURCE_CLASSNAME_DEFAULT);
+    try {
+      Class userAttributeSoureClass = Class.forName(userAttributeSourceClassname);
+
+      UserAttributeSource attributeSource = (UserAttributeSource) userAttributeSoureClass.newInstance();
+
+      Class<? extends UserAttributeSourceParams> attributeSourceParamsClass = attributeSource.getParamsClass();
+
+      UserAttributeSourceParams uaParams = attributeSourceParamsClass.newInstance();
+      uaParams.init(solrParams);
+
+      attributeSource.init(uaParams, combinedAttributes);
+
+      return attributeSource;
+
+    } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "User Attribute Source Class misconfigured", e);
+    }
+
+  }
+
+  @SuppressWarnings("rawtypes")
+  @Override
+  public void prepare(ResponseBuilder rb, String userName) throws IOException {
+    ModifiableSolrParams params = new ModifiableSolrParams(rb.req.getParams());
+
+    Multimap<String, String> userAttributes = userAttributeSource.getAttributesForUser(userName);
+    for (FieldToAttributeMapping mapping: fieldAttributeMappings) {
+      String filterQuery = buildFilterQueryString(userAttributes, mapping);
+      LOG.debug("Adding filter clause : {}", filterQuery);
+      params.add("fq", filterQuery);
+    }
+
+    rb.req.setParams(params);
+
+  }
+
+  private String buildFilterQueryString(Multimap<String, String> userAttributes, FieldToAttributeMapping mapping) {
+    String fieldName = mapping.getFieldName();
+    Collection<String> attributeValues = getUserAttributesForField(userAttributes, mapping);
+    switch (mapping.getFilterType()) {
+      case OR:
+        return buildSimpleORFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+      case AND:
+        return buildSubsetFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+      case GTE:
+        return buildGreaterThanFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+      case LTE:
+        return buildLessThanFilterQuery(fieldName, attributeValues, mapping.getAcceptEmpty(), mapping.getAllUsersValue(), mapping.getExtraOpts());
+      default:
+        return null;
+    }
+  }
+
+  @VisibleForTesting
+  static Collection<String> getUserAttributesForField(Multimap<String, String> userAttributes, FieldToAttributeMapping mapping) {
+    Set<String> userAttributesSubset = new HashSet<>();
+    Pattern regex = mapping.getAttrValueRegex();
+    for (String attributeName : mapping.getAttributes()) {
+      // If there is a regex to apply, we'll apply it to each element and add each element individually
+      // If there isn't, we'll add the required attributes in bulk
+      if (regex != null && regex.pattern().length() > 0) {
+        for (String value : userAttributes.get(attributeName)) {
+          String group = null;
+          Matcher matcher = regex.matcher(value);
+          // We're allowing Regex groups to extract specific values from the returned value
+          // for example extracting common names out of a distinguished name
+          // As an assumption, we're going to pull the last not-null value from the matcher and use that
+          if (matcher.find()) {
+            for (int i = matcher.groupCount(); i >= 0; i--) {
+              group = matcher.group(i);
+              if (group != null) {
+                break;
+              }
+            }
+          }
+          if (group != null) {
+            userAttributesSubset.add(group);
+          }
+        }
+      } else {
+          userAttributesSubset.addAll(userAttributes.get(attributeName));
+      }
+    }
+    return userAttributesSubset;
+  }
+
+  private String buildSimpleORFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+    StringBuilder s = new StringBuilder();
+    for (String attributeValue : attributeValues) {
+      s.append(fieldName).append(":\"").append(attributeValue).append("\" ");
+    }
+    if (allUsersValue != null && !allUsersValue.equals("")) {
+        s.append(fieldName).append(":\"").append(allUsersValue).append("\" ");
+    }
+    if (allowEmptyField) {
+      s.append("(*:* AND -").append(fieldName).append(":*) ");
+    }
+    if (extraOpts != null && !extraOpts.equals("")) {
+      s.append(extraOpts + " ");
+    }
+    s.deleteCharAt(s.length() - 1);
+    return s.toString();
+  }
+
+  private String buildSubsetFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+    StringBuilder s = new StringBuilder();
+    s.append("{!").append(andQParserName)
+        .append(" set_field=").append(fieldName)
+        .append(" set_value=").append(Joiner.on(',').join(attributeValues));
+    if (allUsersValue != null && !allUsersValue.equals("")) {
+        s.append(" wildcard_token=").append(allUsersValue);
+    }
+    if (allowEmptyField) {
+      s.append(" allow_missing_val=true");
+    } else {
+      s.append(" allow_missing_val=false");
+    }
+    if (extraOpts != null && !extraOpts.equals("")) {
+      s.append(" " + extraOpts);
+    }
+    s.append("}");
+    return s.toString();
+  }
+
+  private String buildGreaterThanFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+    String value;
+    if (attributeValues.size() == 1) {
+        value = attributeValues.iterator().next();
+    } else if (allUsersValue != null && !allUsersValue.equals("")) {
+        value = allUsersValue;
+    } else {
+        throw new IllegalArgumentException("Greater Than Filter Query cannot be built for field " + fieldName);
+    }
+    StringBuilder extraClause = new StringBuilder();
+    if (allowEmptyField) {
+        extraClause.append(" (*:* AND -").append(fieldName).append(":*)");
+    }
+    if (extraOpts != null && !extraOpts.equals("")) {
+      extraClause.append(" ").append(extraOpts);
+    }
+    return fieldName + ":[" + value + " TO *]" + extraClause.toString();
+  }
+
+  private String buildLessThanFilterQuery(String fieldName, Collection<String> attributeValues, boolean allowEmptyField, String allUsersValue, String extraOpts) {
+    String value;
+    if (attributeValues.size() == 1) {
+        value = attributeValues.iterator().next();
+    } else if (allUsersValue != null && !allUsersValue.equals("")) {
+        value = allUsersValue;
+    } else {
+        throw new IllegalArgumentException("Less Than Filter Query cannot be built for field " + fieldName);
+    }
+    StringBuilder extraClause = new StringBuilder();
+    if (allowEmptyField) {
+      extraClause.append(" (*:* AND -").append(fieldName).append(":*)");
+    }
+    if (extraOpts != null && !extraOpts.equals("")) {
+      extraClause.append(" ").append(extraOpts);
+    }
+    return fieldName + ":[* TO " + value + "]" + extraClause.toString();
+  }
+
+  @Override
+  public void process(ResponseBuilder rb) throws IOException {
+  }
+
+  @Override
+  public String getDescription() {
+    return "Handle Query Document Authorization based on user attributes";
+  }
+
+  public String getSource() {
+    return "$URL$";
+  }
+
+  @SuppressWarnings({"unchecked"})
+  private <T> T checkAndGet(NamedList args, String key) {
+    return (T) Preconditions.checkNotNull(args.get(key));
+  }
+
+  private <T> T getWithDefault(NamedList args, String key, T defaultValue) {
+    T value = (T) args.get(key);
+    if (value == null) {
+      return defaultValue;
+    } else {
+      return value;
+    }
+  }
+
+  @Override
+  public boolean getEnabled() {
+    return enabled;
+  }
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/UserAttributeSource.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/UserAttributeSource.java
new file mode 100644
index 0000000..011c4af
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/UserAttributeSource.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import com.google.common.collect.Multimap;
+
+import java.util.Collection;
+
+/**
+ * Interface for a source for user attributes. Instances are configurable by an implementation
+ * of {@link UserAttributeSourceParams} which can be obtained via getParamsClass.
+ */
+public interface UserAttributeSource {
+    /**
+     * @param userName
+     * @return Multimap of attributes for the given user in the form attributeName->Set(values)
+     */
+    Multimap<String, String> getAttributesForUser(String userName);
+
+    /**
+     * @return The implementation of {@link UserAttributeSourceParams} that is used to configure
+     * this class.
+     */
+    Class<? extends UserAttributeSourceParams> getParamsClass();
+
+    /**
+     * @param params An instance of {@link UserAttributeSourceParams} to configure this class
+     * @param attributes Specifies the possible attributes that will be returned as part of the search
+     */
+    void init(UserAttributeSourceParams params, Collection<String> attributes);
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/UserAttributeSourceParams.java b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/UserAttributeSourceParams.java
new file mode 100644
index 0000000..71d4442
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/main/java/org/apache/solr/handler/component/UserAttributeSourceParams.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.component;
+
+import org.apache.solr.common.params.SolrParams;
+
+/**
+ * Params interface to hold named key-value pairs for configuring {@link UserAttributeSource} subclasses
+ * To be initialized from a {@link SolrParams} object.
+ */
+public interface UserAttributeSourceParams {
+
+  /**
+   * @param solrParams from which to initialize this object
+   */
+  void init(SolrParams solrParams);
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/CachingUserAttributeSourceTest.java b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/CachingUserAttributeSourceTest.java
new file mode 100644
index 0000000..62aadad
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/CachingUserAttributeSourceTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.component;
+
+import com.google.common.base.Ticker;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.times;
+
+public class CachingUserAttributeSourceTest {
+
+  @Mock
+  private UserAttributeSource mockUserAttributeSource;
+
+  @Before
+  public void setup() {
+      MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void testWithMocks(){
+
+    // configure mock LDAP response
+    Multimap<String, String> mockUserAttributes = LinkedListMultimap.create();
+    mockUserAttributes.putAll("attr1", Arrays.asList("A", "B", "C"));
+    mockUserAttributes.put("attr2", "DEF");
+    mockUserAttributes.put("attr3", "3");
+    Mockito.when(mockUserAttributeSource.getAttributesForUser(Mockito.<String>any())).thenReturn(mockUserAttributes);
+
+    CachingUserAttributeSource cachingUserAttributeSource = new CachingUserAttributeSource(mockUserAttributeSource, SolrAttrBasedFilter.CACHE_TTL_DEFAULT, SolrAttrBasedFilter.CACHE_MAX_SIZE_DEFAULT);
+
+    // call caching source a bunch of times...
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user1");
+
+    // ... but make sure underlying source only got called once
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user1");
+
+  }
+
+  @Test
+  public void testCacheSizeWithMocks(){
+
+    // configure mock LDAP response
+    Multimap<String, String> mockUserAttributes = LinkedListMultimap.create();
+    mockUserAttributes.putAll("attr1", Arrays.asList("A", "B", "C"));
+    mockUserAttributes.put("attr2", "DEF");
+    mockUserAttributes.put("attr3", "3");
+    Mockito.when(mockUserAttributeSource.getAttributesForUser(Mockito.<String>any())).thenReturn(mockUserAttributes);
+
+    CachingUserAttributeSource cachingUserAttributeSource = new CachingUserAttributeSource(mockUserAttributeSource, SolrAttrBasedFilter.CACHE_TTL_DEFAULT, 3);
+
+    // call caching source a bunch of times...
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user2");
+    cachingUserAttributeSource.getAttributesForUser("user3");
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user2");
+    cachingUserAttributeSource.getAttributesForUser("user3");
+
+    // ... but make sure underlying source only got called once
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user1");
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user2");
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user3");
+
+    // Now request a fourth user and therefore age out user1
+    cachingUserAttributeSource.getAttributesForUser("user4");
+    cachingUserAttributeSource.getAttributesForUser("user1");
+
+    Mockito.verify(mockUserAttributeSource, times(2)).getAttributesForUser("user1");
+
+
+  }
+
+  @Test
+  public void testCacheTtlWithMocks(){
+
+    // configure mock LDAP response
+    Multimap<String, String> mockUserAttributes = LinkedListMultimap.create();
+    mockUserAttributes.putAll("attr1", Arrays.asList("A", "B", "C"));
+    mockUserAttributes.put("attr2", "DEF");
+    mockUserAttributes.put("attr3", "3");
+    Mockito.when(mockUserAttributeSource.getAttributesForUser(Mockito.<String>any())).thenReturn(mockUserAttributes);
+
+    // Create a cache with a 1s TTL
+    FastForwardTicker time = new FastForwardTicker();
+    CachingUserAttributeSource cachingUserAttributeSource = new CachingUserAttributeSource(mockUserAttributeSource, 1, 100, time);
+
+    // call caching source a bunch of times...
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user2");
+    cachingUserAttributeSource.getAttributesForUser("user3");
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user2");
+    cachingUserAttributeSource.getAttributesForUser("user3");
+
+    // ... but make sure underlying source only got called once
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user1");
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user2");
+    Mockito.verify(mockUserAttributeSource, times(1)).getAttributesForUser("user3");
+
+    // "Wait" for 2 seconds
+    time.fastForward(Duration.ofSeconds(2));
+
+    // Now let the cache age out the entries
+    cachingUserAttributeSource.getAttributesForUser("user1");
+    cachingUserAttributeSource.getAttributesForUser("user2");
+    cachingUserAttributeSource.getAttributesForUser("user3");
+
+    Mockito.verify(mockUserAttributeSource, times(2)).getAttributesForUser("user1");
+    Mockito.verify(mockUserAttributeSource, times(2)).getAttributesForUser("user2");
+    Mockito.verify(mockUserAttributeSource, times(2)).getAttributesForUser("user3");
+
+  }
+
+  private static class FastForwardTicker extends Ticker {
+    private long tnanos = 0L;
+
+    public void fastForward(Duration interval){
+      tnanos += interval.toNanos();
+    }
+
+    @Override
+    public long read() {
+      return tnanos;
+    }
+  }
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/LdapRegexTest.java b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/LdapRegexTest.java
new file mode 100644
index 0000000..5396b59
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/LdapRegexTest.java
@@ -0,0 +1,130 @@
+package org.apache.solr.handler.component;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class LdapRegexTest {
+
+  @Test
+  public void regexSimpleTest() {
+    Multimap<String, String> userAttributes = LinkedListMultimap.create();
+
+    userAttributes.put("attr1", "test1");
+    userAttributes.put("attr2", "test2");
+    userAttributes.put("attr3", "test3");
+    userAttributes.put("attr4", "test4");
+
+    Set<String> expectedResults = new HashSet<>();
+    expectedResults.add("test1");
+    expectedResults.add("test2");
+    expectedResults.add("test3");
+    expectedResults.add("test4");
+
+    String regex = "[A-Za-z0-9]*";
+
+    Collection<String> results = runRegexesForAttributes(userAttributes, regex);
+
+    Assert.assertTrue(results.containsAll(expectedResults));
+    Assert.assertTrue(expectedResults.containsAll(results));
+
+  }
+
+  @Test
+  public void regexRoleIdTest() {
+    Multimap<String, String> userAttributes = LinkedListMultimap.create();
+
+    userAttributes.put("attr1", "some_prefix_r1234");
+    userAttributes.put("attr2", "some_prefix_r2345");
+
+    Set<String> expectedResults = new HashSet<>();
+    expectedResults.add("1234");
+    expectedResults.add("2345");
+
+    String regex = "(some_prefix_r([0-9]+))";
+
+    Collection<String> results = runRegexesForAttributes(userAttributes, regex);
+
+    Assert.assertTrue(results.containsAll(expectedResults));
+    Assert.assertTrue(expectedResults.containsAll(results));
+
+  }
+
+  @Test
+  public void regexDNTest() {
+    Multimap<String, String> userAttributes = LinkedListMultimap.create();
+
+    userAttributes.put("attr1", "CN=JohnDoe, OU=People, DC=apache, DC=com");
+    userAttributes.put("attr2", "O=Apache, CN=JoeBloggs, OU=People, DC=apache, DC=com");
+
+    Set<String> expectedResults = new HashSet<>();
+    expectedResults.add("JohnDoe");
+    expectedResults.add("JoeBloggs");
+
+    String regex = "(CN=([A-Za-z0-9\\-\\_]+),)";
+
+    Collection<String> results = runRegexesForAttributes(userAttributes, regex);
+
+    Assert.assertTrue(results.containsAll(expectedResults));
+    Assert.assertTrue(expectedResults.containsAll(results));
+
+  }
+
+  @Test
+  public void regexComplexTest() {
+    Multimap<String, String> userAttributes = LinkedListMultimap.create();
+
+    userAttributes.put("attr1", "abc123");
+    userAttributes.put("attr2", "O=Sentry, CN=a_b_thisisatest, OU=People, DC=apache, DC=com");
+    userAttributes.put("attr3", "O=Sentry, CN=a_b_test_thisisatestnumber2, OU=People, DC=apache, DC=com");
+
+    Set<String> expectedResults = new HashSet<>();
+    expectedResults.add("abc123");
+    expectedResults.add("thisisatest");
+    expectedResults.add("thisisatestnumber2");
+
+    String regex = "(^[A-Za-z0-9]+$)|(CN=(a_b_(?:test_)?([A-Za-z0-9\\-\\_]{2,})),)";
+
+    Collection<String> results = runRegexesForAttributes(userAttributes, regex);
+
+    Assert.assertTrue(results.containsAll(expectedResults));
+    Assert.assertTrue(expectedResults.containsAll(results));
+  }
+
+  private Collection<String> runRegexesForAttributes(Multimap<String, String> userAttributes, String regex) {
+    String fieldName = "test1";
+    String ldapAttributeNames = StringUtils.join(userAttributes.keySet().toArray(), ",");
+    String filterType = "AND";
+    boolean acceptEmpty = false;
+    String allUsersValue = "N/A";
+    String extraOpts = "";
+
+    FieldToAttributeMapping mapping = new FieldToAttributeMapping(fieldName, ldapAttributeNames, filterType, acceptEmpty, allUsersValue, regex, extraOpts);
+
+    return SolrAttrBasedFilter.getUserAttributesForField(userAttributes, mapping);
+
+  }
+
+}
diff --git a/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/MockUserAttributeSource.java b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/MockUserAttributeSource.java
new file mode 100644
index 0000000..e8953ab
--- /dev/null
+++ b/sentry-solr/solr-sentry-handlers/src/test/java/org/apache/solr/handler/component/MockUserAttributeSource.java
@@ -0,0 +1,55 @@
+package org.apache.solr.handler.component;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.Collection;
+
+public class MockUserAttributeSource implements UserAttributeSource {
+  @Override
+  public Multimap<String, String> getAttributesForUser(String userName) {
+    Multimap<String, String> results = LinkedHashMultimap.create();
+    switch (userName) {
+      case "user1":
+        results.put("attr1", "val1");
+        results.put("attr1", "val2");
+        results.put("attr2", "val1");
+        results.put("attr3", "val1");
+        break;
+      case "user2":
+        results.put("attr1", "val1");
+        results.put("attr1", "val2");
+        results.put("attr1", "val3");
+        results.put("attr1", "val4");
+        break;
+    }
+    return results;
+  }
+
+  @Override
+  public Class<? extends UserAttributeSourceParams> getParamsClass() {
+    return null;
+  }
+
+  @Override
+  public void init(UserAttributeSourceParams params, Collection<String> attributes) {
+
+  }
+
+}
diff --git a/sentry-tests/sentry-tests-solr/pom.xml b/sentry-tests/sentry-tests-solr/pom.xml
index 7c28bda..a5edd3b 100644
--- a/sentry-tests/sentry-tests-solr/pom.xml
+++ b/sentry-tests/sentry-tests-solr/pom.xml
@@ -270,6 +270,12 @@ limitations under the License.
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>org.zapodot</groupId>
+      <artifactId>embedded-ldap-junit</artifactId>
+      <version>0.5.2</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/SolrSentryServiceTestBase.java b/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/SolrSentryServiceTestBase.java
index 09f095a..6fc849b 100644
--- a/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/SolrSentryServiceTestBase.java
+++ b/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/SolrSentryServiceTestBase.java
@@ -95,6 +95,8 @@ public class SolrSentryServiceTestBase extends AbstractSolrSentryTestCase {
                      .resolve("cloud-minimal_subset_match").resolve("conf"))
         .addConfig("cloud-minimal_subset_match_missing_false", TEST_PATH().resolve("configsets")
               .resolve("cloud-minimal_subset_match_missing_false").resolve("conf"))
+        .addConfig("cloud-minimal_abac", TEST_PATH().resolve("configsets")
+              .resolve("cloud-minimal_abac").resolve("conf"))
         .configure();
       log.info("Successfully started Solr service");
 
@@ -222,6 +224,31 @@ public class SolrSentryServiceTestBase extends AbstractSolrSentryTestCase {
     result.put("subset_user_0123", Sets.newHashSet("subset_group0", "subset_group1", "subset_group2", "subset_group3", "subset_nogroup", "subset_delete"));
     result.put("subset_user_no", Sets.newHashSet("subset_nogroup"));
 
+    result.put("abacuser1", Sets.newHashSet("abac_group"));
+    result.put("abacuser2", Sets.newHashSet("abac_group"));
+    result.put("abacuser3", Sets.newHashSet("abac_group"));
+    result.put("abacuser4", Sets.newHashSet("abac_group"));
+    result.put("abacuser5", Sets.newHashSet("abac_group"));
+
+    result.put("lteuser1", Sets.newHashSet("abac_group"));
+    result.put("lteuser2", Sets.newHashSet("abac_group"));
+    result.put("lteuser3", Sets.newHashSet("abac_group"));
+
+    result.put("gteuser1", Sets.newHashSet("abac_group"));
+    result.put("gteuser2", Sets.newHashSet("abac_group"));
+    result.put("gteuser3", Sets.newHashSet("abac_group"));
+
+    result.put("oruser1", Sets.newHashSet("abac_group"));
+    result.put("oruser2", Sets.newHashSet("abac_group"));
+    result.put("oruser3", Sets.newHashSet("abac_group"));
+
+    result.put("anduser1", Sets.newHashSet("abac_group"));
+    result.put("anduser2", Sets.newHashSet("abac_group"));
+    result.put("anduser3", Sets.newHashSet("abac_group"));
+
+    result.put("nesteduser1", Sets.newHashSet("abac_group"));
+    result.put("nesteduser2", Sets.newHashSet("abac_group"));
+
     return Collections.unmodifiableMap(result);
   }
 
diff --git a/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestAbacOperations.java b/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestAbacOperations.java
new file mode 100644
index 0000000..69b9c2f
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestAbacOperations.java
@@ -0,0 +1,465 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sentry.tests.e2e.solr;
+
+import org.apache.sentry.core.common.exception.SentryUserException;
+import org.apache.sentry.core.model.solr.SolrConstants;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrInputDocument;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.zapodot.junit.ldap.EmbeddedLdapRule;
+import org.zapodot.junit.ldap.EmbeddedLdapRuleBuilder;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static org.apache.sentry.tests.e2e.solr.TestSentryServer.ADMIN_USER;
+
+public class TestAbacOperations extends SolrSentryServiceTestBase {
+  private int numDocs = 16;
+
+  private static final String gteFieldName = "grade2";
+  private static final String lteFieldName = "grade1";
+  private static final String orGroupsFieldName = "orGroups";
+  private static final String andGroupsFieldName = "andGroups";
+  private static final String andGroupsCountFieldName = "andGroupsCount";
+
+  public static final String DOMAIN_DSN = "dc=example,dc=com";
+  @Rule
+  public EmbeddedLdapRule embeddedLdapRule = EmbeddedLdapRuleBuilder.newInstance().bindingToPort(10389)
+      .bindingToAddress("localhost").usingDomainDsn(DOMAIN_DSN).withoutDefaultSchema().withSchema("ldap/ldap.schema")
+      .importingLdifs("ldap/ldap.ldiff").build();
+
+  @BeforeClass
+  public static void setupPermissions() throws SentryUserException {
+    grantCollectionPrivileges(ADMIN_USER, ADMIN_ROLE, SolrConstants.ALL, SolrConstants.ALL);
+
+    sentryClient.createRole(ADMIN_USER, "abac_role", COMPONENT_SOLR);
+    sentryClient.grantRoleToGroups(ADMIN_USER, "abac_role", COMPONENT_SOLR, Collections.singleton("abac_group"));
+    grantCollectionPrivileges(ADMIN_USER, "abac_role", "simpleCollection", SolrConstants.QUERY);
+    grantCollectionPrivileges(ADMIN_USER, "abac_role", "lteCollection", SolrConstants.QUERY);
+    grantCollectionPrivileges(ADMIN_USER, "abac_role", "gteCollection", SolrConstants.QUERY);
+    grantCollectionPrivileges(ADMIN_USER, "abac_role", "orGroupsCollection", SolrConstants.QUERY);
+    grantCollectionPrivileges(ADMIN_USER, "abac_role", "andGroupsCollection", SolrConstants.QUERY);
+    grantCollectionPrivileges(ADMIN_USER, "abac_role", "nestedOrGroupsCollection", SolrConstants.QUERY);
+  }
+
+  @Before
+  public void resetAuthenticatedUser() {
+    setAuthenticationUser("admin");
+  }
+
+  @Test
+  public void simpleTest() throws Exception {
+    String collectionName = "simpleCollection";
+    createCollection(ADMIN_USER, collectionName, "cloud-minimal_abac", NUM_SERVERS, 1);
+
+    CloudSolrClient client = cluster.getSolrClient();
+
+    /*abacuser1 has the following:
+        orGroupsAttr: group1
+        orGroupsAttr: group2
+        andGroupsAttr: group11
+        andGroupsAttr: group12
+        lteAttr: THREE
+        gteAttr: THIRTY
+    */
+
+    // Add a set of docs that we know will match
+    ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
+    for (int i = 0; i < numDocs; ++i) {
+      SolrInputDocument doc = new SolrInputDocument();
+      String iStr = Long.toString(i);
+      doc.addField("id", iStr);
+      doc.addField("description", "description" + iStr);
+      doc.addField(lteFieldName, "THREE");
+      doc.addField(gteFieldName, "THIRTY");
+      doc.addField(orGroupsFieldName, "group1");
+      doc.addField(andGroupsFieldName, "group11");
+      doc.addField(andGroupsCountFieldName, 1);
+      docs.add(doc);
+    }
+
+    //Add one further doc that won't match
+    SolrInputDocument doc = new SolrInputDocument();
+    doc.addField("id", numDocs);
+    doc.addField("description", "description" + numDocs);
+    doc.addField(lteFieldName, "ONE");
+    doc.addField(gteFieldName, "FIFTY");
+    doc.addField(orGroupsFieldName, "group9");
+    doc.addField(andGroupsFieldName, "group99");
+    doc.addField(andGroupsCountFieldName, 1);
+    docs.add(doc);
+
+    client.add(collectionName, docs);
+    client.commit(collectionName, true, true);
+
+    QueryRequest request = new QueryRequest(new SolrQuery("*:*"));
+
+    setAuthenticationUser("abacuser1");
+    QueryResponse rsp = request.process(client, collectionName);
+    SolrDocumentList docList = rsp.getResults();
+    assertEquals(numDocs, docList.getNumFound());
+
+  }
+
+  @Test
+  public void lteFilterTest() throws Exception {
+     /*
+       We're going to test with three users, each with different values for lteAttr, ONE, THREE and FIVE
+       All other attributes for those users will be fixed.
+     */
+    String collectionName = "lteCollection";
+    createCollection(ADMIN_USER, collectionName, "cloud-minimal_abac", NUM_SERVERS, 1);
+
+    CloudSolrClient client = cluster.getSolrClient();
+
+    String[] lteValues = {"ONE", "TWO", "THREE", "FOUR", "FIVE"};
+
+    ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
+    for (int i = 0; i < numDocs * lteValues.length; ++i) {
+      SolrInputDocument doc = new SolrInputDocument();
+      String iStr = Long.toString(i);
+      doc.addField("id", iStr);
+      doc.addField("description", "lte Test Doc: " + iStr);
+
+      doc.addField(lteFieldName, lteValues[i % lteValues.length]);
+      doc.addField(gteFieldName, "THIRTY");
+      doc.addField(orGroupsFieldName, "group1");
+      doc.addField(andGroupsFieldName, "group11");
+      doc.addField(andGroupsCountFieldName, 1);
+      docs.add(doc);
+    }
+
+    client.add(collectionName, docs);
+    client.commit(collectionName, true, true);
+
+    QueryRequest request = new QueryRequest(new SolrQuery("*:*"));
+
+    /*
+      lteuser1 has lteAttr of ONE -> Should see numDocs docs
+      lteuser2 has lteAttr of THREE -> Should see numDocs * 3
+      lteuser3 has lteAttr of FIVE -> Should see numDocs * 5 docs
+     */
+
+    setAuthenticationUser("lteuser1");
+    QueryResponse rsp = request.process(client, collectionName);
+    SolrDocumentList docList = rsp.getResults();
+
+    assertEquals(numDocs, docList.getNumFound());
+
+    setAuthenticationUser("lteuser2");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(numDocs * 3, docList.getNumFound());
+
+    setAuthenticationUser("lteuser3");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(numDocs * 5, docList.getNumFound());
+
+  }
+
+  @Test
+  public void gteFilterTest() throws Exception {
+     /*
+       We're going to test with three users, each with different values for gteAttr, TEN, THIRTY and FIFTY
+       All other attributes for those users will be fixed.
+     */
+    String collectionName = "gteCollection";
+    createCollection(ADMIN_USER, collectionName, "cloud-minimal_abac", NUM_SERVERS, 1);
+
+    CloudSolrClient client = cluster.getSolrClient();
+
+    String[] gteValues = {"TEN", "TWENTY", "THIRTY", "FORTY", "FIFTY"};
+
+    ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
+    for (int i = 0; i < numDocs * gteValues.length; ++i) {
+      SolrInputDocument doc = new SolrInputDocument();
+      String iStr = Long.toString(i);
+      doc.addField("id", iStr);
+      doc.addField("description", "gte Test Doc: " + iStr);
+
+      doc.addField(gteFieldName, gteValues[i % gteValues.length]);
+      doc.addField(lteFieldName, "THREE");
+      doc.addField(orGroupsFieldName, "group1");
+      doc.addField(andGroupsFieldName, "group11");
+      doc.addField(andGroupsCountFieldName, 1);
+      docs.add(doc);
+    }
+
+    client.add(collectionName, docs);
+    client.commit(collectionName, true, true);
+
+    QueryRequest request = new QueryRequest(new SolrQuery("*:*"));
+
+    /*
+      gteuser1 has gteAttr of TEN -> Should see numDocs * 5 docs
+      gteuser2 has gteAttr of THIRTY -> Should see numDocs * 3
+      gteuser3 has gteAttr of FIFTY -> Should see numDocs docs
+     */
+
+    setAuthenticationUser("gteuser1");
+    QueryResponse rsp = request.process(client, collectionName);
+    SolrDocumentList docList = rsp.getResults();
+
+    assertEquals(numDocs * 5, docList.getNumFound());
+
+    setAuthenticationUser("gteuser2");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(numDocs * 3, docList.getNumFound());
+
+    setAuthenticationUser("gteuser3");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(numDocs, docList.getNumFound());
+
+  }
+
+  @Test
+  public void orGroupsFilterTest() throws Exception {
+     /*
+       We're going to test with three users, each with different values for orGroups
+       All other attributes for those users will be fixed.
+     */
+    String collectionName = "orGroupsCollection";
+    createCollection(ADMIN_USER, collectionName, "cloud-minimal_abac", NUM_SERVERS, 1);
+
+    CloudSolrClient client = cluster.getSolrClient();
+
+    int[] groupDocCount = new int[3];
+
+    ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
+    for (int i = 0; i < numDocs * 4; ++i) {
+      SolrInputDocument doc = new SolrInputDocument();
+      String iStr = Long.toString(i);
+      doc.addField("id", iStr);
+      doc.addField("description", "Or Groups Test Doc: " + iStr);
+      doc.addField(gteFieldName, "THIRTY");
+      doc.addField(lteFieldName, "THREE");
+      if (i % 2 == 0) {
+        groupDocCount[0]++;
+        doc.addField(orGroupsFieldName, "group1");
+      }
+      if (i % 3 == 0) {
+        doc.addField(orGroupsFieldName, "group2");
+      }
+      if (i % 4 == 0) {
+        doc.addField(orGroupsFieldName, "group3");
+      }
+      if (i % 2 == 0 || i % 3 == 0) {
+        groupDocCount[1]++;
+      }
+      if (i % 2 == 0 || i % 3 == 0 || i % 4 == 0) {
+        groupDocCount[2]++;
+      }
+      doc.addField(andGroupsFieldName, "group11");
+      doc.addField(andGroupsCountFieldName, 1);
+
+      docs.add(doc);
+    }
+
+    client.add(collectionName, docs);
+    client.commit(collectionName, true, true);
+
+    QueryRequest request = new QueryRequest(new SolrQuery("*:*"));
+
+    /*
+      oruser1 has group1
+      oruser2 has group1, group2
+      oruser3 has group1, group2, group3
+     */
+
+    setAuthenticationUser("oruser1");
+    QueryResponse rsp = request.process(client, collectionName);
+    SolrDocumentList docList = rsp.getResults();
+
+    assertEquals(groupDocCount[0], docList.getNumFound());
+
+    setAuthenticationUser("oruser2");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(groupDocCount[1], docList.getNumFound());
+
+    setAuthenticationUser("oruser3");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(groupDocCount[2], docList.getNumFound());
+
+  }
+
+  @Test
+  public void andGroupsFilterTest() throws Exception {
+      /*
+       We're going to test with three users, each with different values for andGroups
+       All other attributes for those users will be fixed.
+     */
+    String collectionName = "andGroupsCollection";
+    createCollection(ADMIN_USER, collectionName, "cloud-minimal_abac", NUM_SERVERS, 1);
+
+    CloudSolrClient client = cluster.getSolrClient();
+
+    int[] groupDocCount = new int[3];
+
+    ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
+    for (int i = 0; i < numDocs * 4; ++i) {
+      int andGroups = 0;
+      SolrInputDocument doc = new SolrInputDocument();
+      String iStr = Long.toString(i);
+      doc.addField("id", iStr);
+      doc.addField("description", "Or Groups Test Doc: " + iStr);
+      doc.addField(gteFieldName, "THIRTY");
+      doc.addField(lteFieldName, "THREE");
+      if (i % 2 == 0) {
+        doc.addField(andGroupsFieldName, "group11");
+        andGroups++;
+      }
+      if (i % 3 == 0) {
+        doc.addField(andGroupsFieldName, "group12");
+        andGroups++;
+      }
+      if (i % 4 == 0) {
+        doc.addField(andGroupsFieldName, "group13");
+        andGroups++;
+      }
+
+      if (i % 2 == 0 && (i % 3 != 0 && i % 4 != 0)) {
+        groupDocCount[0]++;
+      }
+      if ((i % 2 == 0 || i % 3 == 0) && i % 4 != 0) {
+        groupDocCount[1]++;
+      }
+      if (i % 2 == 0 || i % 3 == 0 || i % 4 == 0) {
+        groupDocCount[2]++;
+      }
+      doc.addField(andGroupsCountFieldName, andGroups);
+      doc.addField(orGroupsFieldName, "group1");
+
+      docs.add(doc);
+    }
+
+    client.add(collectionName, docs);
+    client.commit(collectionName, true, true);
+
+    QueryRequest request = new QueryRequest(new SolrQuery("*:*"));
+
+    /*
+      anduser1 has group11
+      anduser2 has group11, group12
+      anduser3 has group11, group12, group13
+     */
+
+    setAuthenticationUser("anduser1");
+    QueryResponse rsp = request.process(client, collectionName);
+    SolrDocumentList docList = rsp.getResults();
+
+    assertEquals(groupDocCount[0], docList.getNumFound());
+
+    setAuthenticationUser("anduser2");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(groupDocCount[1], docList.getNumFound());
+
+    setAuthenticationUser("anduser3");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+    assertEquals(groupDocCount[2], docList.getNumFound());
+
+  }
+
+  @Test
+  public void nestedOrGroupsFilterTest() throws Exception {
+     /*
+       We're going to test with three users, each with different values for orGroups
+       All other attributes for those users will be fixed.
+     */
+    String collectionName = "nestedOrGroupsCollection";
+    createCollection(ADMIN_USER, collectionName, "cloud-minimal_abac", NUM_SERVERS, 1);
+
+    CloudSolrClient client = cluster.getSolrClient();
+
+    int[] groupDocCount = new int[3];
+
+    ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
+    for (int i = 0; i < numDocs * 4; ++i) {
+      SolrInputDocument doc = new SolrInputDocument();
+      String iStr = Long.toString(i);
+      doc.addField("id", iStr);
+      doc.addField("description", "Nested Or Groups Test Doc: " + iStr);
+      doc.addField(gteFieldName, "THIRTY");
+      doc.addField(lteFieldName, "THREE");
+      if (i % 2 == 0) {
+        doc.addField(orGroupsFieldName, "nestedgroup1");
+        doc.addField(orGroupsFieldName, "nestedgroup4");
+      }
+      if (i % 3 == 0) {
+        doc.addField(orGroupsFieldName, "nestedgroup2");
+        doc.addField(orGroupsFieldName, "nestedgroup5");
+      }
+      if (i % 4 == 0) {
+        doc.addField(orGroupsFieldName, "nestedgroup3");
+        doc.addField(orGroupsFieldName, "nestedgroup6");
+      }
+
+      if (i % 2 == 0 || i % 3 == 0 || i % 4 == 0) {
+        groupDocCount[0]++;
+      }
+      doc.addField(andGroupsFieldName, "group11");
+      doc.addField(andGroupsCountFieldName, 1);
+      docs.add(doc);
+    }
+
+    client.add(collectionName, docs);
+    client.commit(collectionName, true, true);
+
+    QueryRequest request = new QueryRequest(new SolrQuery("*:*"));
+
+    /*
+      nesteduser1 has nestedgroup1, nestedgroup2 and nestedgroup3
+      nesteduser2 has nestedgroup4, nestedgroup5 and nestedgroup6
+      oruser1 does not have any of the nested groups so should return zero
+     */
+
+    setAuthenticationUser("oruser1");
+    QueryResponse rsp = request.process(client, collectionName);
+    SolrDocumentList docList = rsp.getResults();
+
+    assertEquals(0, docList.getNumFound());
+
+    setAuthenticationUser("nesteduser1");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+
+    assertEquals(groupDocCount[0], docList.getNumFound());
+
+    setAuthenticationUser("nesteduser2");
+    rsp = request.process(client, collectionName);
+    docList = rsp.getResults();
+
+    assertEquals(groupDocCount[0], docList.getNumFound());
+
+  }
+}
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/ldap/ldap.ldiff b/sentry-tests/sentry-tests-solr/src/test/resources/ldap/ldap.ldiff
new file mode 100644
index 0000000..6f650d2
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/ldap/ldap.ldiff
@@ -0,0 +1,342 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+dn: dc=example,dc=com
+objectClass: domain
+objectClass: top
+dc: example
+
+dn: ou=Users,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: Users
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: Groups
+
+dn: cn=admin,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: admin
+sn: admin
+uid: admin
+userPassword: abcdefg
+
+dn: cn=testGroup1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+member: cn=admin,ou=Users,dc=example,dc=com
+cn: testGroup1
+
+dn: cn=testGroup2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+cn: testGroup2
+
+dn: cn=testGroup3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+cn: testGroup3
+
+dn: cn=testNestedGroup1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+cn: testNestedGroup1
+
+dn: cn=testNestedGroup2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=testNestedGroup1,ou=Groups,dc=example,dc=com
+cn: testNestedGroup2
+
+dn: cn=testNestedGroup3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=testNestedGroup2,ou=Groups,dc=example,dc=com
+cn: testNestedGroup3
+
+dn: cn=abacuser1,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: abacuser1
+sn: one
+uid: abacuser1
+orGroupsAttr: group1
+orGroupsAttr: group2
+andGroupsAttr: group11
+andGroupsAttr: group12
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=lteuser1,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: lteuser1
+sn: lteuser1
+uid: lteuser1
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: ONE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=lteuser2,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: lteuser2
+sn: lteuser2
+uid: lteuser2
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=lteuser3,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: lteuser3
+sn: lteuser3
+uid: lteuser3
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: FIVE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=gteuser1,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: gteuser1
+sn: gteuser1
+uid: gteuser1
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: TEHN
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=gteuser2,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: gteuser2
+sn: gteuser2
+uid: gteuser2
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=gteuser3,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: gteuser3
+sn: gteuser3
+uid: gteuser3
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: FIFTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=oruser1,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: oruser1
+sn: oruser1
+uid: oruser1
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=oruser2,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: oruser2
+sn: oruser2
+uid: oruser2
+orGroupsAttr: group1
+orGroupsAttr: group2
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=oruser3,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: oruser3
+sn: oruser3
+uid: oruser3
+orGroupsAttr: group1
+orGroupsAttr: group2
+orGroupsAttr: group3
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=anduser1,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: anduser1
+sn: anduser1
+uid: anduser1
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=anduser2,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: anduser2
+sn: anduser2
+uid: anduser2
+orGroupsAttr: group1
+andGroupsAttr: group11
+andGroupsAttr: group12
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=anduser3,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: anduser3
+sn: anduser3
+uid: anduser3
+orGroupsAttr: group1
+andGroupsAttr: group11
+andGroupsAttr: group12
+andGroupsAttr: group13
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=testGroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=nestedgroup3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+cn: nestedgroup3
+
+dn: cn=nestedgroup2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=nestedgroup3,ou=Groups,dc=example,dc=com
+cn: nestedgroup2
+
+dn: cn=nestedgroup1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=nestedgroup2,ou=Groups,dc=example,dc=com
+cn: nestedgroup1
+
+
+dn: cn=nesteduser1,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: nesteduser1
+sn: nesteduser1
+uid: nesteduser1
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=nestedgroup1,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
+
+dn: cn=nestedgroup6,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=nestedgroup4,ou=Groups,dc=example,dc=com
+cn: nestedgroup6
+
+dn: cn=nestedgroup5,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=nestedgroup6,ou=Groups,dc=example,dc=com
+cn: nestedgroup5
+
+dn: cn=nestedgroup4,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+objectClass: top
+memberOf: cn=nestedgroup5,ou=Groups,dc=example,dc=com
+cn: nestedgroup4
+
+
+dn: cn=nesteduser2,ou=Users,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: nesteduser2
+sn: nesteduser2
+uid: nesteduser2
+orGroupsAttr: group1
+andGroupsAttr: group11
+lteAttr: THREE
+gteAttr: THIRTY
+memberOf: cn=nestedgroup5,ou=Groups,dc=example,dc=com
+userPassword: abcdefg
\ No newline at end of file
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/ldap/ldap.schema b/sentry-tests/sentry-tests-solr/src/test/resources/ldap/ldap.schema
new file mode 100644
index 0000000..3a169f9
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/ldap/ldap.schema
@@ -0,0 +1,1692 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file contains a set of standard schema definitions from various RFCs and
+# Internet Drafts.  It is not intended to be a complete comprehensive schema
+# for all purposes, but it may be used by the LDAP SDK for cases in which
+# schema information may be required and no other definitions are available.
+#
+# This file has been modified to include custom attributes for the ABAC tests.
+#
+# Definitions in this class come from the following sources:
+# * RFC 2798:
+#   Definition of the inetOrgPerson LDAP Object Class
+# * RFC 3045:
+#   Storing Vendor Information in the LDAP Root DSE
+# * RFC 3112:
+#   LDAP Authentication Password Schema
+# * RFC 3296:
+#   Named Subordinate References in LDAP Directories
+# * RFC 4512:
+#   LDAP Directory Information Models
+# * RFC 4519:
+#   LDAP Schema for User Applications
+# * RFC 4523:
+#   LDAP Schema Definitions for X.509 Certificates
+# * RFC 4524:
+#   COSINE LDAP/X.500 Schema
+# * RFC 4530:
+#   LDAP entryUUID Operational Attribute
+# * RFC 5020:
+#   The LDAP entryDN Operational Attribute
+# * draft-good-ldap-changelog:
+#   Definition of an Object Class to Hold LDAP Change Records
+# * draft-howard-namedobject:
+#   A Structural Object Class for Arbitrary Auxiliary Object Classes
+# * draft-ietf-boreham-numsubordinates:
+#   numSubordinates LDAP Operational Attribute
+# * draft-ietf-ldup-subentry:
+#   LDAP Subentry Schema
+dn: cn=schema
+objectClass: top
+objectClass: ldapSubEntry
+objectClass: subschema
+cn: schema
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.3
+  DESC 'Attribute Type Description'
+  X-ORIGIN 'RFC 4517' )
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.6
+  DESC 'Bit String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.7
+  DESC 'Boolean'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.11
+  DESC 'Country String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.14
+  DESC 'Delivery Method'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.15
+  DESC 'Directory String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.16
+  DESC 'DIT Content Rule Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.17
+  DESC 'DIT Structure Rule Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.12
+  DESC 'DN'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.21
+  DESC 'Enhanced Guide'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.22
+  DESC 'Facsimile Telephone Number'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.23
+  DESC 'Fax'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.24
+  DESC 'Generalized Time'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.25
+  DESC 'Guide'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.26
+  DESC 'IA5 String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.27
+  DESC 'INTEGER'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.28
+  DESC 'JPEG'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.54
+  DESC 'LDAP Syntax Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.30
+  DESC 'Matching Rule Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.31
+  DESC 'Matching Rule Use Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.34
+  DESC 'Name And Optional UID'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.35
+  DESC 'Name Form Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.36
+  DESC 'Numeric String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.37
+  DESC 'Object Class Description'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.40
+  DESC 'Octet String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.38
+  DESC 'OID'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.39
+  DESC 'Other Mailbox'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.41
+  DESC 'Postal Address'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.44
+  DESC 'Printable String'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.58
+  DESC 'Substring Assertion'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.50
+  DESC 'Telephone Number'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.51
+  DESC 'Teletex Terminal Identifier'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.52
+  DESC 'Telex Number'
+  X-ORIGIN 'RFC 4517')
+ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.53
+  DESC 'UTC Time'
+  X-ORIGIN 'RFC 4517')
+matchingRules: ( 2.5.13.16
+  NAME 'bitStringMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.6
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.13
+  NAME 'booleanMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 1.3.6.1.4.1.1466.109.114.1
+  NAME 'caseExactIA5Match'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.5
+  NAME 'caseExactMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.6
+  NAME 'caseExactOrderingMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.7
+  NAME 'caseExactSubstringsMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.58
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 1.3.6.1.4.1.1466.109.114.2
+  NAME 'caseIgnoreIA5Match'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 1.3.6.1.4.1.1466.109.114.3
+  NAME 'caseIgnoreIA5SubstringsMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.58
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.11
+  NAME 'caseIgnoreListMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.41
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.12
+  NAME 'caseIgnoreListSubstringsMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.58
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.2
+  NAME 'caseIgnoreMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.3
+  NAME 'caseIgnoreOrderingMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.4
+  NAME 'caseIgnoreSubstringsMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.58
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.31
+  NAME 'directoryStringFirstComponentMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.1
+  NAME 'distinguishedNameMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.27
+  NAME 'generalizedTimeMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.28
+  NAME 'generalizedTimeOrderingMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.29
+  NAME 'integerFirstComponentMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.14
+  NAME 'integerMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.15
+  NAME 'integerOrderingMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.33
+  NAME 'keywordMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.8
+  NAME 'numericStringMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.36
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.9
+  NAME 'numericStringOrderingMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.36
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.10
+  NAME 'numericStringSubstringsMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.58
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.30
+  NAME 'objectIdentifierFirstComponentMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.0
+  NAME 'objectIdentifierMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.17
+  NAME 'octetStringMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.18
+  NAME 'octetStringOrderingMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.20
+  NAME 'telephoneNumberMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.50
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.21
+  NAME 'telephoneNumberSubstringsMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.58
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.23
+  NAME 'uniqueMemberMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.34
+  X-ORIGIN 'RFC 4517' )
+matchingRules: ( 2.5.13.32
+  NAME 'wordMatch'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4517' )
+attributeTypes: ( 2.5.4.0
+  NAME 'objectClass'
+  EQUALITY objectIdentifierMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.4.1
+  NAME 'aliasedObjectName'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.18.3
+  NAME 'creatorsName'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.55.183.1
+  NAME 'contactPerson'
+  SINGLE-VALUE
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 22.5.183.1
+    NAME 'member'
+    EQUALITY distinguishedNameMatch
+    SYNTAX 1.3.6.1.4.5.1466.115.121.1.12
+    X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 12.5.183.12
+  NAME 'uugid'
+  EQUALITY caseIgnoreMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.62
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 12.5.183.1
+  NAME 'creationDate'
+  SINGLE-VALUE
+  EQUALITY caseIgnoreMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.18.1
+  NAME 'createTimestamp'
+  EQUALITY generalizedTimeMatch
+  ORDERING generalizedTimeOrderingMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.18.4
+  NAME 'modifiersName'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.18.2
+  NAME 'modifyTimestamp'
+  EQUALITY generalizedTimeMatch
+  ORDERING generalizedTimeOrderingMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.9
+  NAME 'structuralObjectClass'
+  EQUALITY objectIdentifierMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.10
+  NAME 'governingStructureRule'
+  EQUALITY integerMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.18.10
+  NAME 'subschemaSubentry'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.6
+  NAME 'objectClasses'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.37
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.5
+  NAME 'attributeTypes'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.3
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.4
+  NAME 'matchingRules'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.30
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.8
+  NAME 'matchingRuleUse'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.31
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.16
+  NAME 'ldapSyntaxes'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.54
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.2
+  NAME 'dITContentRules'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.16
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.1
+  NAME 'dITStructureRules'
+  EQUALITY integerFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.17
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.7
+  NAME 'nameForms'
+  EQUALITY objectIdentifierFirstComponentMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.35
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.6
+  NAME 'altServer'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.5
+  NAME 'namingContexts'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.13
+  NAME 'supportedControl'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.7
+  NAME 'supportedExtension'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.4203.1.3.5
+  NAME 'supportedFeatures'
+  EQUALITY objectIdentifierMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.15
+  NAME 'supportedLDAPVersion'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 1.3.6.1.4.1.1466.101.120.14
+  NAME 'supportedSASLMechanisms'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.4.41
+  NAME 'name'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.15
+  NAME 'businessCategory'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.6
+  NAME 'c'
+  SUP name
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.11
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.3
+  NAME 'cn'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 0.9.2342.19200300.100.1.25
+  NAME 'dc'
+  EQUALITY caseIgnoreIA5Match
+  SUBSTR caseIgnoreIA5SubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.13
+  NAME 'description'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.27
+  NAME 'destinationIndicator'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.44
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.49
+  NAME 'distinguishedName'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.46
+  NAME 'dnQualifier'
+  EQUALITY caseIgnoreMatch
+  ORDERING caseIgnoreOrderingMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.44
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.47
+  NAME 'enhancedSearchGuide'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.21
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.23
+  NAME 'facsimileTelephoneNumber'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.22
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.44
+  NAME 'generationQualifier'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.42
+  NAME 'givenName'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.51
+  NAME 'houseIdentifier'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.43
+  NAME 'initials'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.25
+  NAME 'internationalISDNNumber'
+  EQUALITY numericStringMatch
+  SUBSTR numericStringSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.36
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.7
+  NAME 'l'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.31
+  NAME 'member'
+  SUP distinguishedName
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.10
+  NAME 'o'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.11
+  NAME 'ou'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.32
+  NAME 'owner'
+  SUP distinguishedName
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.19
+  NAME 'physicalDeliveryOfficeName'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.16
+  NAME 'postalAddress'
+  EQUALITY caseIgnoreListMatch
+  SUBSTR caseIgnoreListSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.41
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.17
+  NAME 'postalCode'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.18
+  NAME 'postOfficeBox'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.28
+  NAME 'preferredDeliveryMethod'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.14
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.26
+  NAME 'registeredAddress'
+  SUP postalAddress
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.41
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.33
+  NAME 'roleOccupant'
+  SUP distinguishedName
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.14
+  NAME 'searchGuide'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.25
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.34
+  NAME 'seeAlso'
+  SUP distinguishedName
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.5
+  NAME 'serialNumber'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.44
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.4
+  NAME 'sn'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.8
+  NAME 'st'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.9
+  NAME 'street'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.20
+  NAME 'telephoneNumber'
+  EQUALITY telephoneNumberMatch
+  SUBSTR telephoneNumberSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.50
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.22
+  NAME 'teletexTerminalIdentifier'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.51
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.21
+  NAME 'telexNumber'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.52
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.12
+  NAME 'title'
+  SUP name
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 0.9.2342.19200300.100.1.1
+  NAME 'uid'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.50
+  NAME 'uniqueMember'
+  EQUALITY uniqueMemberMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.34
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.35
+  NAME 'userPassword'
+  EQUALITY octetStringMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 3.1.1.3.1.5.1
+    NAME 'unicodePwd'
+    EQUALITY octetStringMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+    X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.24
+  NAME 'x121Address'
+  EQUALITY numericStringMatch
+  SUBSTR numericStringSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.36
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.24
+  NAME 'creationDate'
+  EQUALITY numericStringMatch
+  SUBSTR numericStringSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.36
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.5.4.45
+  NAME 'x500UniqueIdentifier'
+  EQUALITY bitStringMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.6
+  X-ORIGIN 'RFC 4519' )
+attributeTypes: ( 2.16.840.1.113730.3.1.1
+  NAME 'carLicense'
+  DESC 'vehicle license or registration plate'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2
+  NAME 'departmentNumber'
+  DESC 'identifies a department within an organization'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.241
+  NAME 'displayName'
+  DESC 'preferred name of a person to be used when displaying entries'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.3
+  NAME 'employeeNumber'
+  DESC 'numerically identifies an employee within an organization'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.13.840.1.113730.3.1.3
+  NAME 'userAccountControl'
+  DESC 'the user account control'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.1.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.4
+  NAME 'employeeType'
+  DESC 'type of employment for a person'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 0.9.2342.19200300.100.1.60
+  NAME 'jpegPhoto'
+  DESC 'a JPEG image'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.28
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.39
+  NAME 'preferredLanguage'
+  DESC 'preferred written or spoken language for a person'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.40
+  NAME 'userSMIMECertificate'
+  DESC 'PKCS#7 SignedData used to support S/MIME'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.5
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.16.840.1.113730.3.1.216
+  NAME 'userPKCS12'
+  DESC 'PKCS #12 PFX PDU for exchange of personal identity information'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.5
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 2.5.4.36
+  NAME 'userCertificate'
+  DESC 'X.509 user certificate'
+  EQUALITY certificateExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.8
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 2.5.4.37
+  NAME 'cACertificate'
+  DESC 'X.509 CA certificate'
+  EQUALITY certificateExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.8
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 2.5.4.40
+  NAME 'crossCertificatePair'
+  DESC 'X.509 cross certificate pair'
+  EQUALITY certificatePairExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.10
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 2.5.4.39
+  NAME 'certificateRevocationList'
+  DESC 'X.509 certificate revocation list'
+  EQUALITY certificateListExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.9
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 2.5.4.38
+  NAME 'authorityRevocationList'
+  DESC 'X.509 authority revocation list'
+  EQUALITY certificateListExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.9
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 2.5.4.53
+  NAME 'deltaRevocationList'
+  DESC 'X.509 delta revocation list'
+  EQUALITY certificateListExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.9
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 2.5.4.52
+  NAME 'supportedAlgorithms'
+  DESC 'X.509 supported algorithms'
+  EQUALITY algorithmIdentifierMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.49
+  X-ORIGIN 'RFC 4523' )
+attributeTypes: ( 0.9.2342.19200300.100.1.37
+  NAME 'associatedDomain'
+  EQUALITY caseIgnoreIA5Match
+  SUBSTR caseIgnoreIA5SubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.38
+  NAME 'associatedName'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.48
+  NAME 'buildingName'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.43
+  NAME 'co'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.14
+  NAME 'documentAuthor'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.11
+  NAME 'documentIdentifier'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.15
+  NAME 'documentLocation'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.56
+  NAME 'documentPublisher'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.12
+  NAME 'documentTitle'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.13
+  NAME 'documentVersion'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.5
+  NAME 'drink'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.20
+  NAME 'homePhone'
+  EQUALITY telephoneNumberMatch
+  SUBSTR telephoneNumberSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.50
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.39
+  NAME 'homePostalAddress'
+  EQUALITY caseIgnoreListMatch
+  SUBSTR caseIgnoreListSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.41
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.9
+  NAME 'host'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.4
+  NAME 'info'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.3
+  NAME 'mail'
+  EQUALITY caseIgnoreIA5Match
+  SUBSTR caseIgnoreIA5SubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.10
+  NAME 'manager'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.41
+  NAME 'mobile'
+  EQUALITY telephoneNumberMatch
+  SUBSTR telephoneNumberSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.50
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.45
+  NAME 'organizationalStatus'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.42
+  NAME 'pager'
+  EQUALITY telephoneNumberMatch
+  SUBSTR telephoneNumberSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.50
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.40
+  NAME 'personalTitle'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.6
+  NAME 'roomNumber'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.6
+  NAME 'sAMAccountName'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.110.1.6
+  NAME 'distinguishedName'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.9.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.6
+  NAME 'userPrincipalName'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.21
+  NAME 'secretary'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.44
+  NAME 'uniqueIdentifier'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.8
+  NAME 'userClass'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
+  X-ORIGIN 'RFC 4524' )
+attributeTypes: ( 0.9.2342.19200300.100.1.55
+  NAME 'audio'
+  EQUALITY octetStringMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{250000}
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 0.9.2342.19200300.100.1.7
+  NAME 'photo'
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 1.3.6.1.4.1.250.1.57
+  NAME 'labeledURI'
+  EQUALITY caseExactMatch
+  SUBSTR caseExactSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'RFC 2798' )
+attributeTypes: ( 1.3.6.1.1.20
+  NAME 'entryDN'
+  DESC 'DN of the entry'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 5020' )
+attributeTypes: ( 2.16.840.1.113730.3.1.34
+  NAME 'ref'
+  DESC 'named reference - a labeledURI'
+  EQUALITY caseExactMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  USAGE distributedOperation
+  X-ORIGIN 'RFC 3296' )
+attributeTypes: ( 1.3.6.1.1.4
+  NAME 'vendorName'
+  EQUALITY 1.3.6.1.4.1.1466.109.114.1
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 3045' )
+attributeTypes: ( 1.3.6.1.1.5
+  NAME 'vendorVersion'
+  EQUALITY 1.3.6.1.4.1.1466.109.114.1
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 3045' )
+attributeTypes: ( 1.3.6.1.1.16.4
+  NAME 'entryUUID'
+  DESC 'UUID of the entry'
+  EQUALITY uuidMatch
+  ORDERING uuidOrderingMatch
+  SYNTAX 1.3.6.1.1.16.1
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'RFC 4530' )
+attributeTypes: ( 1.3.6.1.4.1.453.16.2.103
+  NAME 'numSubordinates'
+  DESC 'count of immediate subordinates'
+  EQUALITY integerMatch
+  ORDERING integerOrderingMatch
+  SYNTAX 1.3.6.1.4.1.453.16.2.103
+  SINGLE-VALUE
+   NO-USER-MODIFICATION
+  USAGE directoryOperation
+  X-ORIGIN 'draft-ietf-boreham-numsubordinates' )
+attributeTypes: ( 1.3.6.1.4.1.7628.5.4.1
+  NAME 'inheritable'
+  SYNTAX BOOLEAN
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE dSAOperation
+  X-ORIGIN 'draft-ietf-ldup-subentry' )
+attributeTypes: ( 1.3.6.1.4.1.7628.5.4.2
+  NAME 'blockInheritance'
+  SYNTAX BOOLEAN
+  SINGLE-VALUE
+  NO-USER-MODIFICATION
+  USAGE dSAOperation
+  X-ORIGIN 'draft-ietf-ldup-subentry' )
+attributeTypes: ( 2.16.840.1.113730.3.1.5
+  NAME 'changeNumber'
+  DESC 'a number which uniquely identifies a change made to a directory entry'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  EQUALITY integerMatch
+  ORDERING integerOrderingMatch
+  SINGLE-VALUE
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.6
+  NAME 'targetDN'
+  DESC 'the DN of the entry which was modified'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.7
+  NAME 'changeType'
+  DESC 'the type of change made to an entry'
+  EQUALITY caseIgnoreMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.8
+  NAME 'changes'
+  DESC 'a set of changes to apply to an entry'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.9
+  NAME 'newRDN'
+  DESC 'the new RDN of an entry which is the target of a modrdn operation'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.10
+  NAME 'deleteOldRDN'
+  DESC 'a flag which indicates if the old RDN should be retained as an
+        attribute of the entry'
+  EQUALITY booleanMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+  SINGLE-VALUE
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.11
+  NAME 'newSuperior'
+  DESC 'the new parent of an entry which is the target of a moddn operation'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  SINGLE-VALUE
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 2.16.840.1.113730.3.1.35
+  NAME 'changelog'
+  DESC 'the distinguished name of the entry which contains the set of entries
+        comprising the server changelog'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'draft-good-ldap-changelog' )
+attributeTypes: ( 1.3.6.1.4.1.4203.1.3.3
+  NAME 'supportedAuthPasswordSchemes'
+  DESC 'supported password storage schemes'
+  EQUALITY caseExactIA5Match
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32}
+  USAGE dSAOperation
+  X-ORIGIN 'RFC 3112' )
+attributeTypes: ( 1.3.6.1.4.1.4203.1.3.4
+  NAME 'authPassword'
+  DESC 'password authentication information'
+  EQUALITY 1.3.6.1.4.1.4203.1.2.2
+  SYNTAX 1.3.6.1.4.1.4203.1.1.2
+  X-ORIGIN 'RFC 3112' )
+attributeTypes: ( 2.16.840.1.113730.3.1.55
+  NAME 'aci'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  USAGE directoryOperation
+  X-ORIGIN 'De facto standard' )
+attributeTypes: ( 1.11.111.1.111111.1.1.11
+  NAME 'orGroupsAttr'
+  DESC 'or Groups for testing'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'Sentry Tests' )
+attributeTypes: ( 1.11.111.1.111111.1.1.12
+  NAME 'andGroupsAttr'
+  DESC 'and Groups for testing'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'Sentry Tests' )
+attributeTypes: ( 1.11.111.1.111111.1.1.13
+  NAME 'lteAttr'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SINGLE-VALUE
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+attributeTypes: ( 1.11.111.1.111111.1.1.14
+  NAME 'gteAttr'
+  EQUALITY caseIgnoreMatch
+  SUBSTR caseIgnoreSubstringsMatch
+  SINGLE-VALUE
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'Sentry Tests' )
+attributeTypes: ( 1.11.111.1.111111.1.1.15
+  NAME 'memberOf'
+  EQUALITY distinguishedNameMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 2.5.6.0
+  NAME 'top'
+  ABSTRACT
+  MUST objectClass
+  X-ORIGIN 'RFC 4512' )
+objectClasses: ( 2.5.6.1
+  NAME 'alias'
+  SUP top
+  STRUCTURAL
+  MUST aliasedObjectName
+  X-ORIGIN 'RFC 4512' )
+objectClasses: ( 1.3.6.1.4.1.1466.101.120.111
+  NAME 'extensibleObject'
+  SUP top
+  AUXILIARY
+  X-ORIGIN 'RFC 4512' )
+objectClasses: ( 2.5.20.1
+  NAME 'subschema'
+  AUXILIARY
+  MAY ( dITStructureRules $
+        nameForms $
+        ditContentRules $
+        objectClasses $
+        attributeTypes $
+        matchingRules $
+        matchingRuleUse ) )
+objectClasses: ( 2.5.6.11
+  NAME 'applicationProcess'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( seeAlso $
+        ou $
+        l $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.2
+  NAME 'country'
+  SUP top
+  STRUCTURAL
+  MUST c
+  MAY ( searchGuide $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 1.3.6.1.4.1.1466.344
+  NAME 'dcObject'
+  SUP top
+  AUXILIARY
+  MUST dc
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.14
+  NAME 'device'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( serialNumber $
+        seeAlso $
+        owner $
+        ou $
+        o $
+        l $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.9
+  NAME 'groupOfNames'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( member $
+        memberOf $
+        businessCategory $
+        seeAlso $
+        owner $
+        ou $
+        o $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.17
+  NAME 'groupOfUniqueNames'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( uniqueMember $
+        businessCategory $
+        seeAlso $
+        owner $
+        ou $
+        o $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.3
+  NAME 'locality'
+  SUP top
+  STRUCTURAL
+  MAY ( street $
+        seeAlso $
+        searchGuide $
+        st $
+        l $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.4
+  NAME 'organization'
+  SUP top
+  STRUCTURAL
+  MUST o
+  MAY ( userPassword $
+        searchGuide $
+        seeAlso $
+        businessCategory $
+        x121Address $
+        registeredAddress $
+        destinationIndicator $
+        preferredDeliveryMethod $
+        telexNumber $
+        teletexTerminalIdentifier $
+        telephoneNumber $
+        internationalISDNNumber $
+        facsimileTelephoneNumber $
+        street $
+        postOfficeBox $
+        postalCode $
+        postalAddress $
+        physicalDeliveryOfficeName $
+        st $
+        l $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.6
+  NAME 'person'
+  SUP top
+  STRUCTURAL
+  MUST ( sn $
+         cn )
+  MAY ( userPassword $
+        telephoneNumber $
+        creationDate $
+        seeAlso $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.7
+  NAME 'organizationalPerson'
+  SUP person
+  STRUCTURAL
+  MAY ( title $
+        x121Address $
+        registeredAddress $
+        destinationIndicator $
+        preferredDeliveryMethod $
+        telexNumber $
+        teletexTerminalIdentifier $
+        telephoneNumber $
+        internationalISDNNumber $
+        facsimileTelephoneNumber $
+        street $
+        postOfficeBox $
+        postalCode $
+        postalAddress $
+        physicalDeliveryOfficeName $
+        ou $
+        st $
+        l )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.8
+  NAME 'organizationalRole'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( x121Address $
+        registeredAddress $
+        destinationIndicator $
+        preferredDeliveryMethod $
+        telexNumber $
+        teletexTerminalIdentifier $
+        telephoneNumber $
+        internationalISDNNumber $
+        facsimileTelephoneNumber $
+        seeAlso $
+        roleOccupant $
+        preferredDeliveryMethod $
+        street $
+        postOfficeBox $
+        postalCode $
+        postalAddress $
+        physicalDeliveryOfficeName $
+        ou $
+        st $
+        l $
+        description )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.5
+  NAME 'organizationalUnit'
+  SUP top
+  STRUCTURAL
+  MUST ou
+  MAY ( businessCategory $
+        description $
+        destinationIndicator $
+        facsimileTelephoneNumber $
+        internationalISDNNumber $
+        l $
+        physicalDeliveryOfficeName $
+        postalAddress $
+        postalCode $
+        postOfficeBox $
+        preferredDeliveryMethod $
+        registeredAddress $
+        searchGuide $
+        seeAlso $
+        st $
+        street $
+        telephoneNumber $
+        teletexTerminalIdentifier $
+        telexNumber $
+        userPassword $
+        x121Address )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.5.6.10
+  NAME 'residentialPerson'
+  SUP person
+  STRUCTURAL
+  MUST l
+  MAY ( businessCategory $
+        x121Address $
+        registeredAddress $
+        destinationIndicator $
+        preferredDeliveryMethod $
+        telexNumber $
+        teletexTerminalIdentifier $
+        telephoneNumber $
+        internationalISDNNumber $
+        facsimileTelephoneNumber $
+        preferredDeliveryMethod $
+        street $
+        postOfficeBox $
+        postalCode $
+        postalAddress $
+        physicalDeliveryOfficeName $
+        st $
+        l )
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 1.3.6.1.1.3.1
+  NAME 'uidObject'
+  SUP top
+  AUXILIARY
+  MUST uid
+  X-ORIGIN 'RFC 4519' )
+objectClasses: ( 2.16.840.1.113730.3.2.2
+  NAME 'inetOrgPerson'
+  SUP organizationalPerson
+  STRUCTURAL
+  MAY ( audio $
+        businessCategory $
+        carLicense $
+        departmentNumber $
+        displayName $
+        employeeNumber $
+        employeeType $
+        givenName $
+        homePhone $
+        homePostalAddress $
+        initials $
+        jpegPhoto $
+        labeledURI $
+        mail $
+        manager $
+        mobile $
+        o $
+        pager $
+        photo $
+        roomNumber $
+        secretary $
+        uid $
+        userCertificate $
+        x500uniqueIdentifier $
+        preferredLanguage $
+        userSMIMECertificate $
+        orGroupsAttr $
+        andGroupsAttr $
+        lteAttr $
+        gteAttr $
+        memberOf $
+        userPKCS12 )
+  X-ORIGIN 'RFC 2798' )
+objectClasses: ( 2.5.6.21
+  NAME 'pkiUser'
+  DESC 'X.509 PKI User'
+  SUP top AUXILIARY
+  MAY userCertificate
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.22
+  NAME 'pkiCA'
+  DESC 'X.509 PKI Certificate Authority'
+  SUP top
+  AUXILIARY
+  MAY ( cACertificate $
+        certificateRevocationList $
+        authorityRevocationList $
+        crossCertificatePair )
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.19
+  NAME 'cRLDistributionPoint'
+  DESC 'X.509 CRL distribution point'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( certificateRevocationList $
+        authorityRevocationList $
+        deltaRevocationList )
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.23
+  NAME 'deltaCRL'
+  DESC 'X.509 delta CRL'
+  SUP top
+  AUXILIARY
+  MAY deltaRevocationList
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.15
+  NAME 'strongAuthenticationUser'
+  DESC 'X.521 strong authentication user'
+  SUP top
+  AUXILIARY
+  MUST userCertificate
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.18
+  NAME 'userSecurityInformation'
+  DESC 'X.521 user security information'
+  SUP top
+  AUXILIARY
+  MAY ( supportedAlgorithms )
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.16
+  NAME 'certificationAuthority'
+  DESC 'X.509 certificate authority'
+  SUP top
+  AUXILIARY
+  MUST ( authorityRevocationList $
+         certificateRevocationList $
+         cACertificate )
+  MAY crossCertificatePair
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 2.5.6.16.2
+  NAME 'certificationAuthority-V2'
+  DESC 'X.509 certificate authority, version 2'
+  SUP certificationAuthority
+  AUXILIARY
+  MAY deltaRevocationList
+  X-ORIGIN 'RFC 4523' )
+objectClasses: ( 0.9.2342.19200300.100.4.5
+  NAME 'account'
+  SUP top STRUCTURAL
+  MUST uid
+  MAY ( description $
+        seeAlso $
+        l $
+        o $
+        ou $
+        host )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.6
+  NAME 'document'
+  SUP top STRUCTURAL
+  MUST documentIdentifier
+  MAY ( cn $
+        description $
+        seeAlso $
+        l $
+        o $
+        ou $
+        documentTitle $
+        documentVersion $
+        documentAuthor $
+        documentLocation $
+        documentPublisher )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.9
+  NAME 'documentSeries'
+  SUP top STRUCTURAL
+  MUST cn
+  MAY ( description $
+        l $
+        o $
+        ou $
+        seeAlso $
+        telephonenumber )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.13
+  NAME 'domain'
+  SUP top
+  STRUCTURAL
+  MUST dc
+  MAY ( userPassword $
+        searchGuide $
+        seeAlso $
+        businessCategory $
+        x121Address $
+        registeredAddress $
+        destinationIndicator $
+        preferredDeliveryMethod $
+        telexNumber $
+        teletexTerminalIdentifier $
+        telephoneNumber $
+        internationaliSDNNumber $
+        facsimileTelephoneNumber $
+        street $
+        postOfficeBox $
+        postalCode $
+        postalAddress $
+        physicalDeliveryOfficeName $
+        st $
+        l $
+        description $
+        o $
+        associatedName )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.17
+  NAME 'domainRelatedObject'
+  SUP top
+  AUXILIARY
+  MUST associatedDomain
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.18
+  NAME 'friendlyCountry'
+  SUP country
+  STRUCTURAL
+  MUST co
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.14
+  NAME 'rFC822localPart'
+  SUP domain
+  STRUCTURAL
+  MAY ( cn $
+        description $
+        destinationIndicator $
+        facsimileTelephoneNumber $
+        internationaliSDNNumber $
+        physicalDeliveryOfficeName $
+        postalAddress $
+        postalCode $
+        postOfficeBox $
+        preferredDeliveryMethod $
+        registeredAddress $
+        seeAlso $
+        sn $
+        street $
+        telephoneNumber $
+        teletexTerminalIdentifier $
+        telexNumber $
+        x121Address )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19211300.100.4.7
+  NAME 'room'
+  SUP top
+  STRUCTURAL
+  MUST cn
+  MAY ( roomNumber $
+        description $
+        seeAlso $
+        telephoneNumber )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19211300.100.4.7
+  NAME 'ad'
+  SUP top
+  STRUCTURAL
+  MUST ( sAMAccountName $
+         unicodePwd $
+         userAccountControl $
+         distinguishedName )
+  MAY ( userPrincipalName $
+        description )
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 0.9.2342.19200300.100.4.19
+  NAME 'simpleSecurityObject'
+  SUP top
+  AUXILIARY
+  MUST userPassword
+  X-ORIGIN 'RFC 4524' )
+objectClasses: ( 2.16.840.1.113730.3.2.6
+  NAME 'referral'
+  DESC 'named subordinate reference object'
+  STRUCTURAL
+  MUST ref
+  X-ORIGIN 'RFC 3296' )
+objectClasses: ( 1.3.6.1.4.1.5322.13.1.1
+  NAME 'namedObject'
+  SUP top
+  STRUCTURAL MAY cn
+  X-ORIGIN 'draft-howard-namedobject' )
+objectClasses: ( 2.16.840.1.113719.2.142.6.1.1
+  NAME 'ldapSubEntry'
+  DESC 'LDAP Subentry class, version 1'
+  SUP top
+  STRUCTURAL
+  MAY ( cn )
+  X-ORIGIN 'draft-ietf-ldup-subentry' )
+objectClasses: ( 1.3.6.1.4.1.7628.5.6.1.1
+  NAME 'inheritableLDAPSubEntry'
+  DESC 'Inheritable LDAP Subentry class, version 1'
+  SUP ldapSubEntry
+  STRUCTURAL
+  MUST ( inheritable )
+  MAY  ( blockInheritance )
+  X-ORIGIN 'draft-ietf-ldup-subentry' )
+objectClasses: ( 2.16.840.1.113730.3.2.1
+  NAME 'changeLogEntry'
+  SUP top
+  STRUCTURAL
+  MUST ( changeNumber $
+         targetDN $
+         changeType )
+  MAY ( changes $
+        newRDN $
+        deleteOldRDN $
+        newSuperior )
+  X-ORIGIN 'draft-good-ldap-changelog' )
+objectClasses: ( 1.3.6.1.4.1.4203.1.4.7
+  NAME 'authPasswordObject'
+  DESC 'authentication password mix in class'
+  AUXILIARY
+  MAY authPassword
+  X-ORIGIN 'RFC 3112' )
+##############################
+# Registered Service Schema
+##############################
+objectClasses: ( 1.6.7.9.0.1.4563.1.4.7
+  NAME 'casRegisteredService'
+  SUP top
+  STRUCTURAL
+  MUST ( uid $
+         description )
+  X-ORIGIN 'RFC 3112' )
+##############################
+# User Service Details Schema
+##############################
+objectClasses: ( 1.6.7.9.0.1.4563.1.4.7
+  NAME 'casServiceUserDetails'
+  SUP top
+  STRUCTURAL
+  MUST (  uid $
+          contactPerson $
+          member $
+          creationDate $
+          uugid )
+  X-ORIGIN 'RFC 3112' )
\ No newline at end of file
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/enumsConfig.xml b/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/enumsConfig.xml
new file mode 100644
index 0000000..4749c46
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/enumsConfig.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Simple enumerations -->
+<enumsConfig>
+    <enum name="grading1">
+        <value>ONE</value>
+        <value>TWO</value>
+        <value>THREE</value>
+        <value>FOUR</value>
+        <value>FIVE</value>
+    </enum>
+    <enum name="grading2">
+        <value>TEN</value>
+        <value>TWENTY</value>
+        <value>THIRTY</value>
+        <value>FORTY</value>
+        <value>FIFTY</value>
+    </enum>
+    <enum name="dispositionProcess">
+        <value>DELETE</value>
+        <value>REVIEW</value>
+    </enum>
+</enumsConfig>
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/schema.xml b/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/schema.xml
new file mode 100644
index 0000000..40b864f
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/schema.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<schema name="minimal" version="1.1">
+  <fieldType name="string" class="solr.StrField"/>
+  <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+  <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+  <fieldType name="date" class="solr.TrieDateField" precisionStep="0" positionIncrementGap="0"/>
+
+  <fieldType name="grading1" class="solr.EnumField" enumsConfig="enumsConfig.xml" enumName="grading1"/>
+  <fieldType name="grading2" class="solr.EnumField" enumsConfig="enumsConfig.xml" enumName="grading2"/>
+  <fieldType name="dispositionProcess" class="solr.EnumField" enumsConfig="enumsConfig.xml" enumName="dispositionProcess"/>
+
+  <dynamicField name="*" type="string" indexed="true" stored="true"/>
+  <!-- for versioning -->
+  <field name="_version_" type="long" indexed="true" stored="true"/>
+  <field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
+  <field name="id" type="string" indexed="true" stored="true"/>
+  <field name="grade1" type="grading1" indexed="true" stored="true"/>
+  <field name="grade2" type="grading2" indexed="true" stored="true"/>
+  <field name="andGroups" type="string" indexed="true" stored="true" docValues="true" multiValued="true"/>
+  <field name="andGroupsCount" type="long" indexed="true" stored="true" />
+  <field name="orGroups" type="string" indexed="true" stored="true" docValues="true" multiValued="true"/>
+
+  <uniqueKey>id</uniqueKey>
+</schema>
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/solrconfig.xml b/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/solrconfig.xml
new file mode 100644
index 0000000..b3175a5
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/configsets/cloud-minimal_abac/conf/solrconfig.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" ?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Minimal solrconfig.xml with /select, /admin and /update only -->
+
+<config>
+
+  <dataDir>${solr.data.dir:}</dataDir>
+
+  <directoryFactory name="DirectoryFactory"
+                    class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
+  <schemaFactory class="ClassicIndexSchemaFactory"/>
+
+  <luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
+
+  <updateHandler class="solr.DirectUpdateHandler2">
+    <commitWithin>
+      <softCommit>${solr.commitwithin.softcommit:true}</softCommit>
+    </commitWithin>
+    <updateLog></updateLog>
+  </updateHandler>
+
+   <requestDispatcher handleSelect="false" >
+     <requestParsers enableRemoteStreaming="true"
+                     multipartUploadLimitInKB="2048000"
+                     formdataUploadLimitInKB="2048"
+                     addHttpRequestToContext="true"/>
+
+    <httpCaching never304="true" />
+  </requestDispatcher>
+
+  <requestHandler name="/select" class="solr.SearchHandler">
+    <lst name="defaults">
+      <str name="echoParams">explicit</str>
+      <str name="indent">true</str>
+      <str name="df">text</str>
+    </lst>
+    <arr name="first-components">
+      <str>queryDocAuthorization</str>
+    </arr>
+  </requestHandler>
+
+  <requestHandler name="/get" class="solr.RealTimeGetHandler">
+     <lst name="defaults">
+       <str name="omitHeader">true</str>
+       <str name="wt">json</str>
+       <str name="indent">true</str>
+     </lst>
+     <arr name="first-components">
+       <str>queryDocAuthorization</str>
+     </arr>
+  </requestHandler>
+
+  <queryParser name="subset" class="org.apache.solr.handler.component.SubsetQueryPlugin"/>
+
+  <searchComponent name="queryDocAuthorization" class="org.apache.solr.handler.component.SolrAttrBasedFilter">
+    <bool name="enabled">true</bool>
+    <!-- caching parameters -->
+    <bool name="cache_enabled">true</bool>
+    <long name="cache_ttl_seconds">20</long>
+    <long name="cache_max_size">10</long>
+    <!-- LDAP parameters -->
+    <str name="ldapProviderUrl">ldap://localhost:10389</str>
+    <str name="ldapAuthType">simple</str>
+    <str name="ldapAdminUser">cn=admin,ou=Users,dc=example,dc=com</str>
+    <str name="ldapAdminPassword"><![CDATA[abcdefg]]></str>
+    <str name="ldapBaseDN">dc=example,dc=com</str>
+    <str name="ldapUserSearchFilter"><![CDATA[(uid={0})]]></str>
+    <bool name="ldapNestedGroupsEnabled">true</bool>
+
+    <str name="andQParser">subset</str>
+    <!-- field mappings -->
+    <lst name="field_attr_mappings">
+      <lst name="orGroups">
+        <str name="attr_names">orGroupsAttr,memberOf</str>
+        <str name="filter_type">OR</str>
+        <str name="value_filter_regex">(^[A-Za-z0-9]+$)|(cn=([A-Za-z0-9\-\_]+),)</str>
+      </lst>
+      <lst name="andGroups">
+        <str name="attr_names">andGroupsAttr</str>
+        <str name="filter_type">AND</str>
+        <str name="extra_opts">count_field=andGroupsCount</str>
+      </lst>
+      <lst name="grade1">
+        <str name="attr_names">lteAttr</str>
+        <str name="filter_type">LTE</str>
+      </lst>
+      <lst name="grade2">
+        <str name="attr_names">gteAttr</str>
+        <str name="filter_type">GTE</str>
+      </lst>
+    </lst>
+  </searchComponent>
+
+
+</config>


Mime
View raw message