();
+ String ldapUrl = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_URL);
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put(Context.PROVIDER_URL, ldapUrl);
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_CREDENTIALS, password);
+ env.put(Context.SECURITY_PRINCIPAL, principal);
+ LOG.debug("Connecting using principal {} to ldap url {}", principal, ldapUrl);
+ return new InitialDirContext(env);
+ }
+}
diff --git service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java
new file mode 100644
index 0000000..5232e3e
--- /dev/null
+++ service/src/java/org/apache/hive/service/auth/ldap/LdapUtils.java
@@ -0,0 +1,228 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hive.service.ServiceUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Static utility methods related to LDAP authentication module.
+ */
+public final class LdapUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LdapUtils.class);
+
+ /**
+ * Extracts a base DN from the provided distinguished name.
+ *
+ * Example:
+ *
+ * "ou=CORP,dc=mycompany,dc=com" is the base DN for "cn=user1,ou=CORP,dc=mycompany,dc=com"
+ *
+ * @param dn distinguished name
+ * @return base DN
+ */
+ public static String extractBaseDn(String dn) {
+ final int indexOfFirstDelimiter = dn.indexOf(",");
+ if (indexOfFirstDelimiter > -1) {
+ return dn.substring(indexOfFirstDelimiter + 1);
+ }
+ return null;
+ }
+
+ /**
+ * Extracts the first Relative Distinguished Name (RDN).
+ *
+ * Example:
+ *
+ * For DN "cn=user1,ou=CORP,dc=mycompany,dc=com" this method will return "cn=user1"
+ * @param dn distinguished name
+ * @return first RDN
+ */
+ public static String extractFirstRdn(String dn) {
+ return dn.substring(0, dn.indexOf(","));
+ }
+
+ /**
+ * Extracts username from user DN.
+ *
+ * Examples:
+ *
+ * LdapUtils.extractUserName("UserName") = "UserName"
+ * LdapUtils.extractUserName("UserName@mycorp.com") = "UserName"
+ * LdapUtils.extractUserName("cn=UserName,dc=mycompany,dc=com") = "UserName"
+ *
+ * @param userDn
+ * @return
+ */
+ public static String extractUserName(String userDn) {
+ if (!isDn(userDn) && !hasDomain(userDn)) {
+ return userDn;
+ }
+
+ int domainIdx = ServiceUtils.indexOfDomainMatch(userDn);
+ if (domainIdx > 0) {
+ return userDn.substring(0, domainIdx);
+ }
+
+ if (userDn.contains("=")) {
+ return userDn.substring(userDn.indexOf("=") + 1, userDn.indexOf(","));
+ }
+ return userDn;
+ }
+
+ /**
+ * Gets value part of the first attribute in the provided RDN.
+ *
+ * Example:
+ *
+ * For RDN "cn=user1,ou=CORP" this method will return "user1"
+ * @param rdn Relative Distinguished Name
+ * @return value part of the first attribute
+ */
+ public static String getShortName(String rdn) {
+ return ((rdn.split(","))[0].split("="))[1];
+ }
+
+ /**
+ * Check for a domain part in the provided username.
+ *
+ * Example:
+ *
+ *
+ * LdapUtils.hasDomain("user1@mycorp.com") = true
+ * LdapUtils.hasDomain("user1") = false
+ *
+ * @param userName username
+ * @return true if {@code userName} contains {@code @} part
+ */
+ public static boolean hasDomain(String userName) {
+ return (ServiceUtils.indexOfDomainMatch(userName) > 0);
+ }
+
+ /**
+ * Detects DN names.
+ *
+ * Example:
+ *
+ *
+ * LdapUtils.isDn("cn=UserName,dc=mycompany,dc=com") = true
+ * LdapUtils.isDn("user1") = false
+ *
+ * @param name name to be checked
+ * @return true if the provided name is a distinguished name
+ */
+ public static boolean isDn(String name) {
+ return name.contains("=");
+ }
+
+ /**
+ * Reads and parses DN patterns from Hive configuration.
+ *
+ * If no patterns are provided in the configuration, then the base DN will be used.
+ * @param conf Hive configuration
+ * @param var variable to be read
+ * @return a list of DN patterns
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN
+ */
+ public static List parseDnPatterns(HiveConf conf, HiveConf.ConfVars var) {
+ String patternsString = conf.getVar(var);
+ List result = new ArrayList<>();
+ if (StringUtils.isBlank(patternsString)) {
+ String defaultBaseDn = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN);
+ String guidAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY);
+ if (StringUtils.isNotBlank(defaultBaseDn)) {
+ result.add(guidAttr + "=%s," + defaultBaseDn);
+ }
+ } else {
+ String[] patterns = patternsString.split(":");
+ for (String pattern : patterns) {
+ if (pattern.contains(",") && pattern.contains("=")) {
+ result.add(pattern);
+ } else {
+ LOG.warn("Unexpected format for " + var + "..ignoring " + pattern);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static String patternToBaseDn(String pattern) {
+ if (pattern.contains("=%s")) {
+ return pattern.split(",", 2)[1];
+ }
+ return pattern;
+ }
+
+ /**
+ * Converts a collection of Distinguished Name patterns to a collection of base DNs.
+ * @param patterns Distinguished Name patterns
+ * @return a list of base DNs
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN
+ */
+ public static List patternsToBaseDns(Collection patterns) {
+ List result = new ArrayList<>();
+ for (String pattern : patterns) {
+ result.add(patternToBaseDn(pattern));
+ }
+ return result;
+ }
+
+ /**
+ * Creates a list of principals to be used for user authentication.
+ * @param conf Hive configuration
+ * @param user username
+ * @return a list of user's principals
+ */
+ public static List createCandidatePrincipals(HiveConf conf, String user) {
+ if (hasDomain(user) || isDn(user)) {
+ return Collections.singletonList(user);
+ }
+
+ String ldapDomain = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_DOMAIN);
+ if (StringUtils.isNotBlank(ldapDomain)) {
+ return Collections.singletonList(user + "@" + ldapDomain);
+ }
+
+ List userPatterns = parseDnPatterns(conf,
+ HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN);
+ if (userPatterns.isEmpty()) {
+ return Collections.singletonList(user);
+ }
+
+ List candidatePrincipals = new ArrayList<>();
+ for (String userPattern : userPatterns) {
+ candidatePrincipals.add(userPattern.replaceAll("%s", user));
+ }
+ return candidatePrincipals;
+ }
+
+ private LdapUtils() {
+ }
+}
diff --git service/src/java/org/apache/hive/service/auth/ldap/Query.java service/src/java/org/apache/hive/service/auth/ldap/Query.java
new file mode 100644
index 0000000..d3c3d9b
--- /dev/null
+++ service/src/java/org/apache/hive/service/auth/ldap/Query.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.hive.service.auth.ldap;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.directory.SearchControls;
+import org.stringtemplate.v4.ST;
+
+/**
+ * The object that encompasses all components of a Directory Service search query.
+ *
+ * @see LdapSearch
+ */
+public final class Query {
+
+ private final String filter;
+ private final SearchControls controls;
+
+ /**
+ * Constructs an instance of Directory Service search query.
+ * @param filter search filter
+ * @param controls search controls
+ */
+ public Query(String filter, SearchControls controls) {
+ this.filter = filter;
+ this.controls = controls;
+ }
+
+ /**
+ * Returns search filter.
+ * @return search filter
+ */
+ public String getFilter() {
+ return filter;
+ }
+
+ /**
+ * Returns search controls.
+ * @return search controls
+ */
+ public SearchControls getControls() {
+ return controls;
+ }
+
+ /**
+ * Creates Query Builder.
+ * @return query builder.
+ */
+ public static QueryBuilder builder() {
+ return new QueryBuilder();
+ }
+
+ /**
+ * A builder of the {@link Query}.
+ */
+ public static final class QueryBuilder {
+
+ private ST filterTemplate;
+ private final SearchControls controls = new SearchControls();
+ private final List returningAttributes = new ArrayList<>();
+
+ private QueryBuilder() {
+ controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ controls.setReturningAttributes(new String[0]);
+ }
+
+ /**
+ * Sets search filter template.
+ * @param filterTemplate search filter template
+ * @return the current instance of the builder
+ */
+ public QueryBuilder filter(String filterTemplate) {
+ this.filterTemplate = new ST(filterTemplate);
+ return this;
+ }
+
+ /**
+ * Sets mapping between names in the search filter template and actual values.
+ * @param key marker in the search filter template.
+ * @param value actual value
+ * @return the current instance of the builder
+ */
+ public QueryBuilder map(String key, String value) {
+ filterTemplate.add(key, value);
+ return this;
+ }
+
+ /**
+ * Sets attribute that should be returned in results for the query.
+ * @param attributeName attribute name
+ * @return the current instance of the builder
+ */
+ public QueryBuilder returnAttribute(String attributeName) {
+ returningAttributes.add(attributeName);
+ return this;
+ }
+
+ /**
+ * Sets the maximum number of entries to be returned as a result of the search.
+ *
+ * 0 indicates no limit: all entries will be returned.
+ * @param limit The maximum number of entries that will be returned.
+ * @return the current instance of the builder
+ */
+ public QueryBuilder limit(int limit) {
+ controls.setCountLimit(limit);
+ return this;
+ }
+
+ private void validate() {
+ Preconditions.checkArgument(filterTemplate != null,
+ "filter is required for LDAP search query");
+ }
+
+ private String createFilter() {
+ return filterTemplate.render();
+ }
+
+ private void updateControls() {
+ if (!returningAttributes.isEmpty()) {
+ controls.setReturningAttributes(returningAttributes
+ .toArray(new String[returningAttributes.size()]));
+ }
+ }
+
+ /**
+ * Builds an instance of {@link Query}.
+ * @return configured directory service query
+ */
+ public Query build() {
+ validate();
+ String filter = createFilter();
+ updateControls();
+ return new Query(filter, controls);
+ }
+ }
+}
diff --git service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
new file mode 100644
index 0000000..24e4ff6
--- /dev/null
+++ service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
@@ -0,0 +1,131 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import com.google.common.base.Strings;
+import org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * A factory for common types of directory service search queries.
+ */
+public final class QueryFactory {
+
+ private final String guidAttr;
+ private final String groupClassAttr;
+ private final String groupMembershipAttr;
+
+ /**
+ * Constructs the factory based on provided Hive configuration.
+ * @param conf Hive configuration
+ */
+ public QueryFactory(HiveConf conf) {
+ guidAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY);
+ groupClassAttr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY);
+ groupMembershipAttr = conf.getVar(
+ HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY);
+ }
+
+ /**
+ * Returns a query for finding Group DN based on group unique ID.
+ * @param groupId group unique identifier
+ * @return an instance of {@link Query}
+ */
+ public Query findGroupDnById(String groupId) {
+ return Query.builder()
+ .filter("(&(objectClass=)(=))")
+ .map("guidAttr", guidAttr)
+ .map("groupClassAttr", groupClassAttr)
+ .map("groupID", groupId)
+ .build();
+ }
+
+ /**
+ * Returns a query for finding user DN based on user RDN.
+ * @param userRdn user RDN
+ * @return an instance of {@link Query}
+ */
+ public Query findUserDnByRdn(String userRdn) {
+ return Query.builder()
+ .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))"
+ + "())")
+ .map("userRdn", userRdn)
+ .build();
+ }
+
+ /**
+ * Returns a query for finding user DN based on DN pattern.
+ *
+ * Name of this method was derived from the original implementation of LDAP authentication.
+ * This method should be replaced by {@link QueryFactory#findUserDnByRdn(java.lang.String).
+ *
+ * @param rdn user RDN
+ * @return an instance of {@link Query}
+ */
+ public Query findDnByPattern(String rdn) {
+ return Query.builder()
+ .filter("()")
+ .map("rdn", rdn)
+ .build();
+ }
+
+ /**
+ * Returns a query for finding user DN based on user unique name.
+ * @param userName user unique name (uid or sAMAccountName)
+ * @return an instance of {@link Query}
+ */
+ public Query findUserDnByName(String userName) {
+ return Query.builder()
+ .filter("(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))"
+ + "(|(uid=)(sAMAccountName=)))")
+ .map("userName", userName)
+ .build();
+ }
+
+ /**
+ * Returns a query for finding groups to which the user belongs.
+ * @param userName username
+ * @param userDn user DN
+ * @return an instance of {@link Query}
+ */
+ public Query findGroupsForUser(String userName, String userDn) {
+ return Query.builder()
+ .filter("(&(objectClass=)(|(=)"
+ + "(=)))")
+ .map("groupClassAttr", groupClassAttr)
+ .map("groupMembershipAttr", groupMembershipAttr)
+ .map("userName", userName)
+ .map("userDn", userDn)
+ .build();
+ }
+
+ /**
+ * Returns a query object created for the custom filter.
+ *
+ * This query is configured to return a group membership attribute as part of the search result.
+ * @param searchFilter custom search filter
+ * @return an instance of {@link Query}
+ */
+ public Query customQuery(String searchFilter) {
+ Query.QueryBuilder builder = Query.builder();
+ builder.filter(searchFilter);
+ if (!Strings.isNullOrEmpty(groupMembershipAttr)) {
+ builder.returnAttribute(groupMembershipAttr);
+ }
+ return builder.build();
+ }
+}
diff --git service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java
new file mode 100644
index 0000000..ac02458
--- /dev/null
+++ service/src/java/org/apache/hive/service/auth/ldap/SearchResultHandler.java
@@ -0,0 +1,156 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.SearchResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The object that handles Directory Service search results.
+ * In most cases it converts search results into a list of names in the namespace.
+ */
+public final class SearchResultHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SearchResultHandler.class);
+
+ private final Collection> searchResults;
+
+ /**
+ * Constructs a search result handler object for the provided search results.
+ * @param searchResults directory service search results
+ */
+ public SearchResultHandler(Collection> searchResults) {
+ this.searchResults = searchResults;
+ }
+
+ /**
+ * Returns all entries from the search result.
+ * @return a list of names in the namespace
+ * @throws NamingException
+ */
+ public List getAllLdapNames() throws NamingException {
+ final List result = new ArrayList<>();
+ handle(new RecordProcessor() {
+ @Override
+ public boolean process(SearchResult record) throws NamingException {
+ result.add(record.getNameInNamespace());
+ return true;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Checks whether search result contains exactly one entry.
+ * @return true if the search result contains a single entry.
+ * @throws NamingException
+ */
+ public boolean hasSingleResult() throws NamingException {
+ List allResults = getAllLdapNames();
+ return allResults != null && allResults.size() == 1;
+ }
+
+ /**
+ * Returns a single entry from the search result.
+ * Throws {@code NamingException} if the search result doesn't contain exactly one entry.
+ * @return name in the namespace
+ * @throws NamingException
+ */
+ public String getSingleLdapName() throws NamingException {
+ List allLdapNames = getAllLdapNames();
+ if (allLdapNames.size() == 1) {
+ return allLdapNames.get(0);
+ }
+ throw new NamingException("Single result was expected");
+ }
+
+ /**
+ * Returns all entries and all attributes for these entries.
+ * @return a list that includes all entries and all attributes from these entries.
+ * @throws NamingException
+ */
+ public List getAllLdapNamesAndAttributes() throws NamingException {
+ final List result = new ArrayList<>();
+ handle(new RecordProcessor() {
+ @Override
+ public boolean process(SearchResult record) throws NamingException {
+ result.add(record.getNameInNamespace());
+ NamingEnumeration extends Attribute> allAttributes = record.getAttributes().getAll();
+ while(allAttributes.hasMore()) {
+ Attribute attribute = allAttributes.next();
+ result.add((String)attribute.get());
+ }
+ return true;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Allows for custom processing of the search results.
+ * @param processor {@link RecordProcessor} implementation
+ * @throws NamingException
+ */
+ public void handle(RecordProcessor processor) throws NamingException {
+ try {
+ for (NamingEnumeration searchResult : searchResults) {
+ while (searchResult.hasMore()) {
+ if (!processor.process(searchResult.next())) {
+ return;
+ }
+ }
+ }
+ } finally {
+ for (NamingEnumeration searchResult : searchResults) {
+ try {
+ searchResult.close();
+ } catch (NamingException ex) {
+ LOG.warn("Failed to close LDAP search result", ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * An interface used by {@link SearchResultHandler} for processing records of
+ * a {@link SearchResult} on a per-record basis.
+ *
+ * Implementations of this interface perform the actual work of processing each record,
+ * but don't need to worry about exception handling, closing underlying data structures,
+ * and combining results from several search requests.
+ * {@see SearchResultHandler}
+ */
+ public interface RecordProcessor {
+
+ /**
+ * Implementations must implement this method to process each record in {@link SearchResult}.
+ * @param record the {@code SearchResult} to precess
+ * @return {@code true} to continue processing, {@code false} to stop iterating
+ * over search results
+ * @throws NamingException
+ */
+ boolean process(SearchResult record) throws NamingException;
+ }
+}
diff --git service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java
new file mode 100644
index 0000000..5a91235
--- /dev/null
+++ service/src/java/org/apache/hive/service/auth/ldap/UserFilterFactory.java
@@ -0,0 +1,75 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for a {@link Filter} based on a list of allowed users.
+ *
+ * The produced filter object filters out all users that are not on the provided in
+ * Hive configuration list.
+ * @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER
+ */
+public final class UserFilterFactory implements FilterFactory {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Filter getInstance(HiveConf conf) {
+ Collection userFilter = conf.getStringCollection(
+ HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER.varname);
+
+ if (userFilter.isEmpty()) {
+ return null;
+ }
+
+ return new UserFilter(userFilter);
+ }
+
+ private static final class UserFilter implements Filter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UserFilter.class);
+
+ private final Set userFilter = new HashSet<>();
+
+ UserFilter(Collection userFilter) {
+ for (String userFilterItem : userFilter) {
+ this.userFilter.add(userFilterItem.toLowerCase());
+ }
+ }
+
+ @Override
+ public void apply(DirSearch ldap, String user) throws AuthenticationException {
+ LOG.info("Authenticating user '{}' using user filter", user);
+ String userName = LdapUtils.extractUserName(user).toLowerCase();
+ if (!userFilter.contains(userName)) {
+ LOG.info("Authentication failed based on user membership");
+ throw new AuthenticationException("Authentication failed: "
+ + "User not a member of specified list");
+ }
+ }
+ }
+}
diff --git service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java
new file mode 100644
index 0000000..60801bb
--- /dev/null
+++ service/src/java/org/apache/hive/service/auth/ldap/UserSearchFilterFactory.java
@@ -0,0 +1,55 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+
+/**
+ * A factory for a {@link Filter} that check whether provided user could be found in the directory.
+ *
+ * The produced filter object filters out all users that are not found in the directory.
+ */
+public final class UserSearchFilterFactory implements FilterFactory {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Filter getInstance(HiveConf conf) {
+ return new UserSearchFilter();
+ }
+
+ private static final class UserSearchFilter implements Filter {
+ @Override
+ public void apply(DirSearch client, String user) throws AuthenticationException {
+ try {
+ String userDn = client.findUserDn(user);
+
+ // This should not be null because we were allowed to bind with this username
+ // safe check in case we were able to bind anonymously.
+ if (userDn == null) {
+ throw new AuthenticationException("Authentication failed: User search failed");
+ }
+ } catch (NamingException e) {
+ throw new AuthenticationException("LDAP Authentication failed for user", e);
+ }
+ }
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
index 089a059..23a048a 100644
--- service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
+++ service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
@@ -225,7 +225,6 @@ public static void init() throws Exception {
hiveConf = new HiveConf();
ldapProvider = new LdapAuthenticationProviderImpl(hiveConf);
- ldapProvider.init(hiveConf);
}
@AfterClass
@@ -259,7 +258,7 @@ private static void initLdapAtn(Map hiveProperties)
}
}
- ldapProvider.init(hiveConf);
+ ldapProvider = new LdapAuthenticationProviderImpl(hiveConf);
}
@Test
diff --git service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
index f276906..fd6ef44 100644
--- service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
+++ service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java
@@ -15,51 +15,240 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.hive.service.auth;
+import java.io.IOException;
+import java.util.Arrays;
+import javax.naming.NamingException;
import javax.security.sasl.AuthenticationException;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-
-import junit.framework.TestCase;
import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hive.service.auth.ldap.DirSearch;
+import org.apache.hive.service.auth.ldap.DirSearchFactory;
+import org.apache.hive.service.auth.ldap.LdapSearchFactory;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestLdapAuthenticationProviderImpl {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ public HiveConf conf;
+ public LdapAuthenticationProviderImpl auth;
+
+ @Mock
+ public DirSearchFactory factory;
+
+ @Mock
+ public DirSearch search;
+
+ @Before
+ public void setup() throws AuthenticationException {
+ conf = new HiveConf();
+ conf.set("hive.root.logger", "DEBUG,console");
+ conf.set("hive.server2.authentication.ldap.url", "localhost");
+ when(factory.getInstance(any(HiveConf.class), anyString(), anyString())).thenReturn(search);
+ }
+
+ @Test
+ public void authenticateGivenBlankPassword() throws Exception {
+ auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory());
+ expectAuthenticationExceptionForInvalidPassword();
+ auth.Authenticate("user", "");
+ }
+
+ @Test
+ public void authenticateGivenStringWithNullCharacterForPassword() throws Exception {
+ auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory());
+ expectAuthenticationExceptionForInvalidPassword();
+ auth.Authenticate("user", "\0");
+ }
+
+ @Test
+ public void authenticateGivenNullForPassword() throws Exception {
+ auth = new LdapAuthenticationProviderImpl(conf, new LdapSearchFactory());
+ expectAuthenticationExceptionForInvalidPassword();
+ auth.Authenticate("user", null);
+ }
+
+ @Test
+ public void testAuthenticateNoUserOrGroupFilter() throws NamingException, AuthenticationException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "cn=%s,ou=Users,dc=mycorp,dc=com:cn=%s,ou=PowerUsers,dc=mycorp,dc=com");
-public class TestLdapAuthenticationProviderImpl extends TestCase {
+ DirSearchFactory factory = mock(DirSearchFactory.class);
- private static HiveConf hiveConf;
- private static byte[] hiveConfBackup;
+ when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+
+ when(factory.getInstance(conf, "cn=user1,ou=PowerUsers,dc=mycorp,dc=com", "Blah")).thenReturn(search);
+ when(factory.getInstance(conf, "cn=user1,ou=Users,dc=mycorp,dc=com", "Blah")).thenThrow(AuthenticationException.class);
- @Override
- public void setUp() throws Exception {
- hiveConf = new HiveConf();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- hiveConf.writeXml(baos);
- baos.close();
- hiveConfBackup = baos.toByteArray();
- hiveConf.set("hive.server2.authentication.ldap.url", "localhost");
- FileOutputStream fos = new FileOutputStream(new File(hiveConf.getHiveSiteLocation().toURI()));
- hiveConf.writeXml(fos);
- fos.close();
+ auth = new LdapAuthenticationProviderImpl(conf, factory);
+ auth.Authenticate("user1", "Blah");
+
+ verify(factory, times(2)).getInstance(isA(HiveConf.class), anyString(), eq("Blah"));
+ verify(search, atLeastOnce()).close();
+ }
+
+ @Test
+ public void testAuthenticateWhenUserFilterPasses() throws NamingException, AuthenticationException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER,
+ "user1,user2");
+
+ when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+ when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com");
+
+ authenticateUserAndCheckSearchIsClosed("user1");
+ authenticateUserAndCheckSearchIsClosed("user2");
+ }
+
+ @Test
+ public void testAuthenticateWhenUserSearchFails() throws NamingException, AuthenticationException, IOException {
+ thrown.expect(AuthenticationException.class);
+
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+ when(search.findUserDn("user1")).thenReturn(null);
+
+ authenticateUserAndCheckSearchIsClosed("user1");
+ }
+
+ @Test
+ public void testAuthenticateWhenUserFilterFails() throws NamingException, AuthenticationException, IOException {
+ thrown.expect(AuthenticationException.class);
+
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+ when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com");
+
+ authenticateUserAndCheckSearchIsClosed("user3");
+ }
+
+ @Test
+ public void testAuthenticateWhenGroupFilterPasses() throws NamingException, AuthenticationException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2");
+
+ when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+ when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com");
+
+ when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"))
+ .thenReturn(Arrays.asList(
+ "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+ "cn=group1,ou=Groups,dc=mycorp,dc=com"));
+ when(search.findGroupsForUser("cn=user2,ou=PowerUsers,dc=mycorp,dc=com"))
+ .thenReturn(Arrays.asList(
+ "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+ "cn=group2,ou=Groups,dc=mycorp,dc=com"));
+
+ authenticateUserAndCheckSearchIsClosed("user1");
+ authenticateUserAndCheckSearchIsClosed("user2");
}
+
+ @Test
+ public void testAuthenticateWhenUserAndGroupFiltersPass() throws NamingException, AuthenticationException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+ when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+ when(search.findUserDn("user2")).thenReturn("cn=user2,ou=PowerUsers,dc=mycorp,dc=com");
+
+ when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"))
+ .thenReturn(Arrays.asList(
+ "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+ "cn=group1,ou=Groups,dc=mycorp,dc=com"));
+ when(search.findGroupsForUser("cn=user2,ou=PowerUsers,dc=mycorp,dc=com"))
+ .thenReturn(Arrays.asList(
+ "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+ "cn=group2,ou=Groups,dc=mycorp,dc=com"));
- public void testLdapEmptyPassword() {
- LdapAuthenticationProviderImpl ldapImpl = new LdapAuthenticationProviderImpl(hiveConf);
+ authenticateUserAndCheckSearchIsClosed("user1");
+ authenticateUserAndCheckSearchIsClosed("user2");
+ }
+
+ @Test
+ public void testAuthenticateWhenUserFilterPassesAndGroupFilterFails()
+ throws NamingException, AuthenticationException, IOException {
+ thrown.expect(AuthenticationException.class);
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group1,group2");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+ when(search.findUserDn("user1")).thenReturn("cn=user1,ou=PowerUsers,dc=mycorp,dc=com");
+
+ when(search.findGroupsForUser("cn=user1,ou=PowerUsers,dc=mycorp,dc=com"))
+ .thenReturn(Arrays.asList(
+ "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+ "cn=OtherGroup,ou=Groups,dc=mycorp,dc=com"));
+
+ authenticateUserAndCheckSearchIsClosed("user1");
+ }
+
+ @Test
+ public void testAuthenticateWhenUserFilterFailsAndGroupFilterPasses()
+ throws NamingException, AuthenticationException, IOException {
+ thrown.expect(AuthenticationException.class);
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "group3");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user1,user2");
+
+ when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com");
+
+ when(search.findGroupsForUser("cn=user3,ou=PowerUsers,dc=mycorp,dc=com"))
+ .thenReturn(Arrays.asList(
+ "cn=testGroup,ou=Groups,dc=mycorp,dc=com",
+ "cn=group3,ou=Groups,dc=mycorp,dc=com"));
+
+ authenticateUserAndCheckSearchIsClosed("user3");
+ }
+
+ @Test
+ public void testAuthenticateWhenCustomQueryFilterPasses() throws NamingException, AuthenticationException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycorp,dc=com");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY,
+ "(&(objectClass=person)(|(memberOf=CN=Domain Admins,CN=Users,DC=apache,DC=org)(memberOf=CN=Administrators,CN=Builtin,DC=apache,DC=org)))");
+
+ when(search.executeCustomQuery(anyString())).thenReturn(Arrays.asList(
+ "cn=user1,ou=PowerUsers,dc=mycorp,dc=com",
+ "cn=user2,ou=PowerUsers,dc=mycorp,dc=com"));
+
+ authenticateUserAndCheckSearchIsClosed("user1");
+ }
+
+ @Test
+ public void testAuthenticateWhenCustomQueryFilterFailsAndUserFilterPasses() throws NamingException, AuthenticationException, IOException {
+ thrown.expect(AuthenticationException.class);
+
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycorp,dc=com");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY,
+ "(&(objectClass=person)(|(memberOf=CN=Domain Admins,CN=Users,DC=apache,DC=org)(memberOf=CN=Administrators,CN=Builtin,DC=apache,DC=org)))");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "user3");
+
+ when(search.findUserDn("user3")).thenReturn("cn=user3,ou=PowerUsers,dc=mycorp,dc=com");
+ when(search.executeCustomQuery(anyString())).thenReturn(Arrays.asList(
+ "cn=user1,ou=PowerUsers,dc=mycorp,dc=com",
+ "cn=user2,ou=PowerUsers,dc=mycorp,dc=com"));
+
+ authenticateUserAndCheckSearchIsClosed("user3");
+ }
+
+ private void expectAuthenticationExceptionForInvalidPassword() {
+ thrown.expect(AuthenticationException.class);
+ thrown.expectMessage("a null or blank password has been provided");
+ }
+
+ private void authenticateUserAndCheckSearchIsClosed(String user) throws IOException {
+ auth = new LdapAuthenticationProviderImpl(conf, factory);
try {
- ldapImpl.Authenticate("user", "");
- assertFalse(true);
- } catch (AuthenticationException e) {
- assertTrue(e.getMessage(), e.getMessage().contains("a null or blank password has been provided"));
- }
- }
-
- @Override
- public void tearDown() throws Exception {
- if(hiveConf != null && hiveConfBackup != null) {
- FileOutputStream fos = new FileOutputStream(new File(hiveConf.getHiveSiteLocation().toURI()));
- fos.write(hiveConfBackup);
- fos.close();
- }
+ auth.Authenticate(user, "password doesn't matter");
+ } finally {
+ verify(search, atLeastOnce()).close();
+ }
}
}
diff --git service/src/test/org/apache/hive/service/auth/ldap/Credentials.java service/src/test/org/apache/hive/service/auth/ldap/Credentials.java
new file mode 100644
index 0000000..ce22b8e
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/Credentials.java
@@ -0,0 +1,41 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+public final class Credentials {
+
+ private final String user;
+ private final String password;
+
+ private Credentials(String user, String password) {
+ this.user = user;
+ this.password = password;
+ }
+
+ public static Credentials of(String user, String password) {
+ return new Credentials(user, password);
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java
new file mode 100644
index 0000000..ab96f9a
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/LdapTestUtils.java
@@ -0,0 +1,116 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.SearchResult;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.mockito.stubbing.OngoingStubbing;
+
+public final class LdapTestUtils {
+
+ private LdapTestUtils() {
+ }
+
+ public static NamingEnumeration mockEmptyNamingEnumeration() throws NamingException {
+ return mockNamingEnumeration(new SearchResult[0]);
+ }
+
+ public static NamingEnumeration mockNamingEnumeration(String... dns) throws NamingException {
+ return mockNamingEnumeration(mockSearchResults(dns).toArray(new SearchResult[0]));
+ }
+
+ public static NamingEnumeration mockNamingEnumeration(SearchResult... searchResults) throws NamingException {
+ NamingEnumeration ne =
+ (NamingEnumeration) mock(NamingEnumeration.class);
+ mockHasMoreMethod(ne, searchResults.length);
+ if (searchResults.length > 0) {
+ List mockedResults = Arrays.asList(searchResults);
+ mockNextMethod(ne, mockedResults);
+ }
+ return ne;
+ }
+
+ public static void mockHasMoreMethod(NamingEnumeration ne, int length) throws NamingException {
+ OngoingStubbing hasMoreStub = when(ne.hasMore());
+ for (int i = 0; i < length; i++) {
+ hasMoreStub = hasMoreStub.thenReturn(true);
+ }
+ hasMoreStub.thenReturn(false);
+ }
+
+ public static void mockNextMethod(NamingEnumeration ne, List searchResults) throws NamingException {
+ OngoingStubbing nextStub = when(ne.next());
+ for (SearchResult searchResult : searchResults) {
+ nextStub = nextStub.thenReturn(searchResult);
+ }
+ }
+
+ public static List mockSearchResults(String[] dns) {
+ List list = new ArrayList<>();
+ for (String dn : dns) {
+ list.add(mockSearchResult(dn, null));
+ }
+ return list;
+ }
+
+ public static SearchResult mockSearchResult(String dn, Attributes attributes) {
+ SearchResult searchResult = mock(SearchResult.class);
+ when(searchResult.getNameInNamespace()).thenReturn(dn);
+ when(searchResult.getAttributes()).thenReturn(attributes);
+ return searchResult;
+ }
+
+ public static Attributes mockEmptyAttributes() throws NamingException {
+ return mockAttributes();
+ }
+
+ public static Attributes mockAttributes(String name, String value) throws NamingException {
+ return mockAttributes(new NameValue(name, value));
+ }
+
+ public static Attributes mockAttributes(String name1, String value1, String name2, String value2) throws NamingException {
+ return mockAttributes(new NameValue(name1, value1), new NameValue(name2, value2));
+ }
+
+ private static Attributes mockAttributes(NameValue... namedValues) throws NamingException {
+ Attributes attributes = new BasicAttributes();
+ for (NameValue namedValue : namedValues) {
+ attributes.put(namedValue.name, namedValue.value);
+ }
+ return attributes;
+ }
+
+ private static final class NameValue {
+ final String name;
+ final String value;
+
+ public NameValue(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.java service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.java
new file mode 100644
index 0000000..de34ab09
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestChainFilter.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.hive.service.auth.ldap;
+
+import java.io.IOException;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestChainFilter {
+
+ private FilterFactory factory;
+ private HiveConf conf;
+
+ @Mock
+ public Filter filter1;
+
+ @Mock
+ public Filter filter2;
+
+ @Mock
+ public Filter filter3;
+
+ @Mock
+ public FilterFactory factory1;
+
+ @Mock
+ public FilterFactory factory2;
+
+ @Mock
+ public FilterFactory factory3;
+
+ @Mock
+ private DirSearch search;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ factory = new ChainFilterFactory(factory1, factory2, factory3);
+ }
+
+ @Test
+ public void testFactoryAllNull() {
+ assertNull(factory.getInstance(conf));
+ }
+
+ @Test
+ public void testFactoryAllEmpty() {
+ FilterFactory emptyFactory = new ChainFilterFactory();
+ assertNull(emptyFactory.getInstance(conf));
+ }
+
+ @Test
+ public void testFactory() throws AuthenticationException {
+ when(factory1.getInstance(any(HiveConf.class))).thenReturn(filter1);
+ when(factory2.getInstance(any(HiveConf.class))).thenReturn(filter2);
+ when(factory3.getInstance(any(HiveConf.class))).thenReturn(filter3);
+
+ Filter filter = factory.getInstance(conf);
+
+ filter.apply(search, "User");
+ verify(filter1, times(1)).apply(search, "User");
+ verify(filter2, times(1)).apply(search, "User");
+ verify(filter3, times(1)).apply(search, "User");
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+ doThrow(AuthenticationException.class).when(filter3).apply((DirSearch) anyObject(), anyString());
+
+ when(factory1.getInstance(any(HiveConf.class))).thenReturn(filter1);
+ when(factory3.getInstance(any(HiveConf.class))).thenReturn(filter3);
+
+ Filter filter = factory.getInstance(conf);
+
+ filter.apply(search, "User");
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java
new file mode 100644
index 0000000..e4514e1
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestCustomQueryFilter.java
@@ -0,0 +1,85 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.io.IOException;
+import java.util.Arrays;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestCustomQueryFilter {
+
+ private static final String USER2_DN = "uid=user2,ou=People,dc=example,dc=com";
+ private static final String USER1_DN = "uid=user1,ou=People,dc=example,dc=com";
+ private static final String CUSTOM_QUERY = "(&(objectClass=person)(|(uid=user1)(uid=user2)))";
+
+ private FilterFactory factory;
+ private HiveConf conf;
+
+ @Mock
+ private DirSearch search;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ conf.set("hive.root.logger", "DEBUG,console");
+ factory = new CustomQueryFilterFactory();
+ }
+
+ @Test
+ public void testFactory() {
+ conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY.varname);
+ assertNull(factory.getInstance(conf));
+
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, CUSTOM_QUERY);
+ assertNotNull(factory.getInstance(conf));
+ }
+
+ @Test
+ public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, CUSTOM_QUERY);
+
+ when(search.executeCustomQuery(eq(CUSTOM_QUERY))).thenReturn(Arrays.asList(USER1_DN, USER2_DN));
+
+ Filter filter = factory.getInstance(conf);
+ filter.apply(search, "user1");
+ filter.apply(search, "user2");
+ }
+
+
+ @Test(expected = AuthenticationException.class)
+ public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_CUSTOMLDAPQUERY, CUSTOM_QUERY);
+
+ when(search.executeCustomQuery(eq(CUSTOM_QUERY))).thenReturn(Arrays.asList(USER1_DN, USER2_DN));
+
+ Filter filter = factory.getInstance(conf);
+ filter.apply(search, "user3");
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
new file mode 100644
index 0000000..7932f26
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestGroupFilter.java
@@ -0,0 +1,101 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.io.IOException;
+import java.util.Arrays;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestGroupFilter {
+
+ private FilterFactory factory;
+ private HiveConf conf;
+
+ @Mock
+ private DirSearch search;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ conf.set("hive.root.logger", "DEBUG,console");
+ factory = new GroupFilterFactory();
+ }
+
+ @Test
+ public void testFactory() {
+ conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER.varname);
+ assertNull(factory.getInstance(conf));
+
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "G1");
+ assertNotNull(factory.getInstance(conf));
+ }
+
+ @Test
+ public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HiveUsers");
+
+ when(search.findUserDn(eq("user1")))
+ .thenReturn("cn=user1,ou=People,dc=example,dc=com");
+ when(search.findUserDn(eq("cn=user2,dc=example,dc=com")))
+ .thenReturn("cn=user2,ou=People,dc=example,dc=com");
+ when(search.findUserDn(eq("user3@mydomain.com")))
+ .thenReturn("cn=user3,ou=People,dc=example,dc=com");
+
+ when(search.findGroupsForUser(eq("cn=user1,ou=People,dc=example,dc=com")))
+ .thenReturn(Arrays.asList(
+ "cn=SuperUsers,ou=Groups,dc=example,dc=com",
+ "cn=Office1,ou=Groups,dc=example,dc=com",
+ "cn=HiveUsers,ou=Groups,dc=example,dc=com",
+ "cn=G1,ou=Groups,dc=example,dc=com"));
+ when(search.findGroupsForUser(eq("cn=user2,ou=People,dc=example,dc=com")))
+ .thenReturn(Arrays.asList(
+ "cn=HiveUsers,ou=Groups,dc=example,dc=com"));
+ when(search.findGroupsForUser(eq("cn=user3,ou=People,dc=example,dc=com")))
+ .thenReturn(Arrays.asList(
+ "cn=HiveUsers,ou=Groups,dc=example,dc=com",
+ "cn=G1,ou=Groups,dc=example,dc=com",
+ "cn=G2,ou=Groups,dc=example,dc=com"));
+
+ Filter filter = factory.getInstance(conf);
+ filter.apply(search, "user1");
+ filter.apply(search, "cn=user2,dc=example,dc=com");
+ filter.apply(search, "user3@mydomain.com");
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER, "HiveUsers");
+
+ when(search.findGroupsForUser(eq("user1"))).thenReturn(Arrays.asList("SuperUsers", "Office1", "G1", "G2"));
+
+ Filter filter = factory.getInstance(conf);
+ filter.apply(search, "user1");
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java
new file mode 100644
index 0000000..d6228cd
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestLdapSearch.java
@@ -0,0 +1,209 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.apache.hive.service.auth.ldap.LdapTestUtils.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestLdapSearch {
+
+ @Mock
+ private DirContext ctx;
+
+ private HiveConf conf;
+ private LdapSearch search;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ }
+
+ @Test
+ public void testClose() throws NamingException {
+ search = new LdapSearch(conf, ctx);
+ search.close();
+ verify(ctx, atLeastOnce()).close();
+ }
+
+ @Test
+ public void testFindUserDnWhenUserDnPositive() throws NamingException {
+ NamingEnumeration searchResult = mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+ .thenReturn(searchResult)
+ .thenThrow(NamingException.class);
+ search = new LdapSearch(conf, ctx);
+ String expected = "CN=User1,OU=org1,DC=foo,DC=bar";
+ String actual = search.findUserDn("CN=User1,OU=org1");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testFindUserDnWhenUserDnNegativeDuplicates() throws NamingException {
+ NamingEnumeration searchResult = mockNamingEnumeration(
+ "CN=User1,OU=org1,DC=foo,DC=bar",
+ "CN=User1,OU=org2,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(searchResult);
+ search = new LdapSearch(conf, ctx);
+ assertNull(search.findUserDn("CN=User1,DC=foo,DC=bar"));
+ }
+
+ @Test
+ public void testFindUserDnWhenUserDnNegativeNone() throws NamingException {
+ NamingEnumeration searchResult = mockEmptyNamingEnumeration();
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class))).thenReturn(searchResult);
+ search = new LdapSearch(conf, ctx);
+ assertNull(search.findUserDn("CN=User1,DC=foo,DC=bar"));
+ }
+
+ @Test
+ public void testFindUserDnWhenUserPatternFoundBySecondPattern() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "CN=%s,OU=org1,DC=foo,DC=bar:CN=%s,OU=org2,DC=foo,DC=bar");
+ NamingEnumeration emptyResult = mockEmptyNamingEnumeration();
+ NamingEnumeration validResult = mockNamingEnumeration("CN=User1,OU=org2,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+ .thenReturn(emptyResult)
+ .thenReturn(validResult);
+ search = new LdapSearch(conf, ctx);
+ String expected = "CN=User1,OU=org2,DC=foo,DC=bar";
+ String actual = search.findUserDn("User1");
+ assertEquals(expected, actual);
+ verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+ verify(ctx).search(eq("OU=org2,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+ }
+
+ @Test
+ public void testFindUserDnWhenUserPatternFoundByFirstPattern() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "CN=%s,OU=org1,DC=foo,DC=bar:CN=%s,OU=org2,DC=foo,DC=bar");
+ NamingEnumeration emptyResult = mockEmptyNamingEnumeration();
+ NamingEnumeration validResult = mockNamingEnumeration("CN=User1,OU=org2,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+ .thenReturn(validResult)
+ .thenReturn(emptyResult);
+ search = new LdapSearch(conf, ctx);
+ String expected = "CN=User1,OU=org2,DC=foo,DC=bar";
+ String actual = search.findUserDn("User1");
+ assertEquals(expected, actual);
+ verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+ }
+
+ @Test
+ public void testFindUserDnWhenUserPatternFoundByUniqueIdentifier() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "CN=%s,OU=org1,DC=foo,DC=bar");
+ NamingEnumeration validResult = mockNamingEnumeration("CN=User1,OU=org1,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+ .thenReturn(null)
+ .thenReturn(validResult);
+ search = new LdapSearch(conf, ctx);
+ String expected = "CN=User1,OU=org1,DC=foo,DC=bar";
+ String actual = search.findUserDn("User1");
+ assertEquals(expected, actual);
+ verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("CN=User1"), any(SearchControls.class));
+ verify(ctx).search(eq("OU=org1,DC=foo,DC=bar"), contains("uid=User1"), any(SearchControls.class));
+ }
+
+ @Test
+ public void testFindUserDnWhenUserPatternFoundByUniqueIdentifierNegativeNone() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "CN=%s,OU=org1,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+ .thenReturn(null)
+ .thenReturn(null);
+ search = new LdapSearch(conf, ctx);
+ assertNull(search.findUserDn("User1"));
+ }
+
+ @Test
+ public void testFindUserDnWhenUserPatternFoundByUniqueIdentifierNegativeMany() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "CN=%s,OU=org1,DC=foo,DC=bar");
+ NamingEnumeration manyResult = mockNamingEnumeration(
+ "CN=User1,OU=org1,DC=foo,DC=bar",
+ "CN=User12,OU=org1,DC=foo,DC=bar");
+ when(ctx.search(anyString(), anyString(), any(SearchControls.class)))
+ .thenReturn(null)
+ .thenReturn(manyResult);
+ search = new LdapSearch(conf, ctx);
+ assertNull(search.findUserDn("User1"));
+ }
+
+ @Test
+ public void testFindGroupsForUser() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPDNPATTERN,
+ "CN=%s,OU=org1,DC=foo,DC=bar");
+
+ NamingEnumeration groupsResult = mockNamingEnumeration("CN=Group1,OU=org1,DC=foo,DC=bar");
+ when(ctx.search(eq("OU=org1,DC=foo,DC=bar"), contains("User1"), any(SearchControls.class)))
+ .thenReturn(groupsResult);
+
+ search = new LdapSearch(conf, ctx);
+
+ List expected = Arrays.asList("CN=Group1,OU=org1,DC=foo,DC=bar");
+ List actual = search.findGroupsForUser("CN=User1,OU=org1,DC=foo,DC=bar");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testExecuteCustomQuery() throws NamingException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=example,dc=com");
+
+ NamingEnumeration customQueryResult = mockNamingEnumeration(
+ mockSearchResult(
+ "uid=group1,ou=Groups,dc=example,dc=com",
+ mockAttributes("member", "uid=user1,ou=People,dc=example,dc=com")),
+ mockSearchResult(
+ "uid=group2,ou=Groups,dc=example,dc=com",
+ mockAttributes("member", "uid=user2,ou=People,dc=example,dc=com"))
+ );
+
+ when(ctx.search(eq("dc=example,dc=com"), anyString(), any(SearchControls.class)))
+ .thenReturn(customQueryResult);
+
+ search = new LdapSearch(conf, ctx);
+
+ List expected = Arrays.asList(
+ "uid=group1,ou=Groups,dc=example,dc=com",
+ "uid=user1,ou=People,dc=example,dc=com",
+ "uid=group2,ou=Groups,dc=example,dc=com",
+ "uid=user2,ou=People,dc=example,dc=com");
+ List actual = search.executeCustomQuery("(&(objectClass=groupOfNames)(|(cn=group1)(cn=group2)))");
+ Collections.sort(expected);
+ Collections.sort(actual);
+ assertEquals(expected, actual);
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.java service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.java
new file mode 100644
index 0000000..07d3e81
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestLdapUtils.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.hive.service.auth.ldap;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestLdapUtils {
+
+ @Test
+ public void testCreateCandidatePrincipalsForUserDn() {
+ HiveConf conf = new HiveConf();
+ String userDn = "cn=user1,ou=CORP,dc=mycompany,dc=com";
+ List expected = Arrays.asList(userDn);
+ List actual = LdapUtils.createCandidatePrincipals(conf, userDn);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testCreateCandidatePrincipalsForUserWithDomain() {
+ HiveConf conf = new HiveConf();
+ String userWithDomain = "user1@mycompany.com";
+ List expected = Arrays.asList(userWithDomain);
+ List actual = LdapUtils.createCandidatePrincipals(conf, userWithDomain);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testCreateCandidatePrincipalsLdapDomain() {
+ HiveConf conf = new HiveConf();
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_DOMAIN, "mycompany.com");
+ List expected = Arrays.asList("user1@mycompany.com");
+ List actual = LdapUtils.createCandidatePrincipals(conf, "user1");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testCreateCandidatePrincipalsUserPatternsDefaultBaseDn() {
+ HiveConf conf = new HiveConf();
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY, "sAMAccountName");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycompany,dc=com");
+ List expected = Arrays.asList("sAMAccountName=user1,dc=mycompany,dc=com");
+ List actual = LdapUtils.createCandidatePrincipals(conf, "user1");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testCreateCandidatePrincipals() {
+ HiveConf conf = new HiveConf();
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN, "dc=mycompany,dc=com");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERDNPATTERN,
+ "cn=%s,ou=CORP1,dc=mycompany,dc=com:cn=%s,ou=CORP2,dc=mycompany,dc=com");
+ List expected = Arrays.asList(
+ "cn=user1,ou=CORP1,dc=mycompany,dc=com",
+ "cn=user1,ou=CORP2,dc=mycompany,dc=com");
+ List actual = LdapUtils.createCandidatePrincipals(conf, "user1");
+ Collections.sort(expected);
+ Collections.sort(actual);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testExtractFirstRdn() {
+ String dn = "cn=user1,ou=CORP1,dc=mycompany,dc=com";
+ String expected = "cn=user1";
+ String actual = LdapUtils.extractFirstRdn(dn);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testExtractBaseDn() {
+ String dn = "cn=user1,ou=CORP1,dc=mycompany,dc=com";
+ String expected = "ou=CORP1,dc=mycompany,dc=com";
+ String actual = LdapUtils.extractBaseDn(dn);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testExtractBaseDnNegative() {
+ String dn = "cn=user1";
+ assertNull(LdapUtils.extractBaseDn(dn));
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java
new file mode 100644
index 0000000..6db866f
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestQuery.java
@@ -0,0 +1,59 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestQuery {
+
+ @Test
+ public void testQueryBuilderFilter() {
+ Query q = Query.builder()
+ .filter("test = query")
+ .map("uid_attr", "uid")
+ .map("value", "Hello!")
+ .build();
+ assertEquals("test uid=Hello! query", q.getFilter());
+ assertEquals(0, q.getControls().getCountLimit());
+ }
+
+ @Test
+ public void testQueryBuilderLimit() {
+ Query q = Query.builder()
+ .filter(",")
+ .map("key1", "value1")
+ .map("key2", "value2")
+ .limit(8)
+ .build();
+ assertEquals("value1,value2", q.getFilter());
+ assertEquals(8, q.getControls().getCountLimit());
+ }
+
+ @Test
+ public void testQueryBuilderReturningAttributes() {
+ Query q = Query.builder()
+ .filter("(query)")
+ .returnAttribute("attr1")
+ .returnAttribute("attr2")
+ .build();
+ assertEquals("(query)", q.getFilter());
+ assertArrayEquals(new String[] {"attr1", "attr2"}, q.getControls().getReturningAttributes());
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
new file mode 100644
index 0000000..90a4b55
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestQueryFactory.java
@@ -0,0 +1,79 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestQueryFactory {
+
+ private QueryFactory queries;
+ private HiveConf conf;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GUIDKEY, "guid");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPCLASS_KEY, "superGroups");
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPMEMBERSHIP_KEY, "member");
+ queries = new QueryFactory(conf);
+ }
+
+ @Test
+ public void testFindGroupDnById() {
+ Query q = queries.findGroupDnById("unique_group_id");
+ String expected = "(&(objectClass=superGroups)(guid=unique_group_id))";
+ String actual = q.getFilter();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testFindUserDnByRdn() {
+ Query q = queries.findUserDnByRdn("cn=User1");
+ String expected = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))(cn=User1))";
+ String actual = q.getFilter();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testFindDnByPattern() {
+ Query q = queries.findDnByPattern("cn=User1");
+ String expected = "(cn=User1)";
+ String actual = q.getFilter();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testFindUserDnByName() {
+ Query q = queries.findUserDnByName("unique_user_id");
+ String expected = "(&(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))(|(uid=unique_user_id)(sAMAccountName=unique_user_id)))";
+ String actual = q.getFilter();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testFindGroupsForUser() {
+ Query q = queries.findGroupsForUser("user_name", "user_Dn");
+ String expected = "(&(objectClass=superGroups)(|(member=user_Dn)(member=user_name)))";
+ String actual = q.getFilter();
+ assertEquals(expected, actual);
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java
new file mode 100644
index 0000000..9a5a86f
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestSearchResultHandler.java
@@ -0,0 +1,218 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.apache.hive.service.auth.ldap.LdapTestUtils.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestSearchResultHandler {
+
+ SearchResultHandler handler;
+
+ @Test
+ public void testHandle() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addSearchResultWithDns("1")
+ .addSearchResultWithDns("2", "3");
+ handler = new SearchResultHandler(resultCollection);
+ List expected = Arrays.asList("1", "2");
+ final List actual = new ArrayList<>();
+ handler.handle(new SearchResultHandler.RecordProcessor() {
+ @Override
+ public boolean process(SearchResult record) throws NamingException {
+ actual.add(record.getNameInNamespace());
+ return actual.size() < 2;
+ }
+ });
+ assertEquals(expected, actual);
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testGetAllLdapNamesNoRecords() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addEmptySearchResult();
+ handler = new SearchResultHandler(resultCollection);
+ List actual = handler.getAllLdapNames();
+ assertEquals("Resultset size", 0, actual.size());
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testGetAllLdapNamesWithExceptionInNamingEnumerationClose() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addSearchResultWithDns("1")
+ .addSearchResultWithDns("2");
+ doThrow(NamingException.class).when(resultCollection.iterator().next()).close();
+ handler = new SearchResultHandler(resultCollection);
+ List actual = handler.getAllLdapNames();
+ assertEquals("Resultset size", 2, actual.size());
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testGetAllLdapNames() throws NamingException {
+ String objectDn1 = "cn=a1,dc=b,dc=c";
+ String objectDn2 = "cn=a2,dc=b,dc=c";
+ String objectDn3 = "cn=a3,dc=b,dc=c";
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addSearchResultWithDns(objectDn1)
+ .addSearchResultWithDns(objectDn2, objectDn3);
+ handler = new SearchResultHandler(resultCollection);
+ List expected = Arrays.asList(objectDn1, objectDn2, objectDn3);
+ Collections.sort(expected);
+ List actual = handler.getAllLdapNames();
+ Collections.sort(actual);
+ assertEquals(expected, actual);
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testGetAllLdapNamesAndAttributes() throws NamingException {
+ SearchResult searchResult1 = mockSearchResult("cn=a1,dc=b,dc=c",
+ mockAttributes("attr1", "attr1value1"));
+ SearchResult searchResult2 = mockSearchResult("cn=a2,dc=b,dc=c",
+ mockAttributes("attr1", "attr1value2", "attr2", "attr2value1"));
+ SearchResult searchResult3 = mockSearchResult("cn=a3,dc=b,dc=c",
+ mockEmptyAttributes());
+
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addSearchResults(searchResult1)
+ .addSearchResults(searchResult2, searchResult3);
+
+ handler = new SearchResultHandler(resultCollection);
+ List expected = Arrays.asList(
+ "cn=a1,dc=b,dc=c", "attr1value1",
+ "cn=a2,dc=b,dc=c", "attr1value2", "attr2value1",
+ "cn=a3,dc=b,dc=c");
+ Collections.sort(expected);
+ List actual = handler.getAllLdapNamesAndAttributes();
+ Collections.sort(actual);
+ assertEquals(expected, actual);
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testHasSingleResultNoRecords() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addEmptySearchResult();
+ handler = new SearchResultHandler(resultCollection);
+ assertFalse(handler.hasSingleResult());
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testHasSingleResult() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addSearchResultWithDns("1");
+ handler = new SearchResultHandler(resultCollection);
+ assertTrue(handler.hasSingleResult());
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test
+ public void testHasSingleResultManyRecords() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addSearchResultWithDns("1")
+ .addSearchResultWithDns("2");
+ handler = new SearchResultHandler(resultCollection);
+ assertFalse(handler.hasSingleResult());
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ @Test(expected = NamingException.class)
+ public void testGetSingleLdapNameNoRecords() throws NamingException {
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addEmptySearchResult();
+ handler = new SearchResultHandler(resultCollection);
+ try {
+ handler.getSingleLdapName();
+ } finally {
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+ }
+
+ @Test
+ public void testGetSingleLdapName() throws NamingException {
+ String objectDn = "cn=a,dc=b,dc=c";
+ MockResultCollection resultCollection = MockResultCollection.create()
+ .addEmptySearchResult()
+ .addSearchResultWithDns(objectDn);
+
+ handler = new SearchResultHandler(resultCollection);
+ String expected = objectDn;
+ String actual = handler.getSingleLdapName();
+ assertEquals(expected, actual);
+ assertAllNamingEnumerationsClosed(resultCollection);
+ }
+
+ private void assertAllNamingEnumerationsClosed(MockResultCollection resultCollection) throws NamingException {
+ for (NamingEnumeration namingEnumeration : resultCollection) {
+ verify(namingEnumeration, atLeastOnce()).close();
+ }
+ }
+
+ private static final class MockResultCollection extends AbstractCollection> {
+
+ List> results = new ArrayList<>();
+
+ static MockResultCollection create() {
+ return new MockResultCollection();
+ }
+
+ MockResultCollection addSearchResultWithDns(String... dns) throws NamingException {
+ results.add(mockNamingEnumeration(dns));
+ return this;
+ }
+
+ MockResultCollection addSearchResults(SearchResult... dns) throws NamingException {
+ results.add(mockNamingEnumeration(dns));
+ return this;
+ }
+
+ MockResultCollection addEmptySearchResult() throws NamingException {
+ addSearchResults();
+ return this;
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return results.iterator();
+ }
+
+ @Override
+ public int size() {
+ return results.size();
+ }
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java
new file mode 100644
index 0000000..f941c9c
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestUserFilter.java
@@ -0,0 +1,75 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.io.IOException;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestUserFilter {
+
+ private FilterFactory factory;
+ private HiveConf conf;
+
+ @Mock
+ private DirSearch search;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ factory = new UserFilterFactory();
+ }
+
+ @Test
+ public void testFactory() {
+ conf.unset(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER.varname);
+ assertNull(factory.getInstance(conf));
+
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1");
+ assertNotNull(factory.getInstance(conf));
+ }
+
+ @Test
+ public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1,User2,uSeR3");
+
+ Filter filter = factory.getInstance(conf);
+ filter.apply(search, "User1");
+ filter.apply(search, "uid=user2,ou=People,dc=example,dc=com");
+ filter.apply(search, "User3@mydomain.com");
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testApplyNegative() throws AuthenticationException, NamingException, IOException {
+ conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERFILTER, "User1,User2");
+
+ Filter filter = factory.getInstance(conf);
+ filter.apply(search, "User3");
+ }
+}
diff --git service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java
new file mode 100644
index 0000000..0ba8371
--- /dev/null
+++ service/src/test/org/apache/hive/service/auth/ldap/TestUserSearchFilter.java
@@ -0,0 +1,79 @@
+/**
+ * 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.hive.service.auth.ldap;
+
+import java.io.IOException;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestUserSearchFilter {
+
+ private FilterFactory factory;
+ private HiveConf conf;
+
+ @Mock
+ private DirSearch search;
+
+ @Before
+ public void setup() {
+ conf = new HiveConf();
+ factory = new UserSearchFilterFactory();
+ }
+
+ @Test
+ public void testFactory() {
+ assertNotNull(factory.getInstance(conf));
+ }
+
+ @Test
+ public void testApplyPositive() throws AuthenticationException, NamingException, IOException {
+ Filter filter = factory.getInstance(conf);
+
+ when(search.findUserDn(anyString())).thenReturn("cn=User1,ou=People,dc=example,dc=com");
+
+ filter.apply(search, "User1");
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testApplyWhenNamingException() throws AuthenticationException, NamingException, IOException {
+ Filter filter = factory.getInstance(conf);
+
+ when(search.findUserDn(anyString())).thenThrow(NamingException.class);
+
+ filter.apply(search, "User3");
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testApplyWhenNotFound() throws AuthenticationException, NamingException, IOException {
+ Filter filter = factory.getInstance(conf);
+
+ when(search.findUserDn(anyString())).thenReturn(null);
+
+ filter.apply(search, "User3");
+ }
+}