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>
|