/**
* 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.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.naming.NamingException;
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 groups.
* <br>
* The produced filter object filters out all users that are not members of at least one of
* the groups provided in Hive configuration.
* @see HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER
*/
public final class GroupFilterFactory implements FilterFactory {
/**
* {@inheritDoc}
*/
@Override
public Filter getInstance(HiveConf conf) {
Collection<String> groupFilter = conf.getStringCollection(
HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPFILTER.varname);
if (groupFilter.isEmpty()) {
return null;
}
if (conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERMEMBERSHIP_KEY) == null) {
return new GroupMembershipKeyFilter(groupFilter);
} else {
return new UserMembershipKeyFilter(groupFilter);
}
}
@VisibleForTesting
static final class GroupMembershipKeyFilter implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(GroupMembershipKeyFilter.class);
private final Set<String> groupFilter = new HashSet<>();
GroupMembershipKeyFilter(Collection<String> groupFilter) {
this.groupFilter.addAll(groupFilter);
}
@Override
public void apply(DirSearch ldap, String user) throws AuthenticationException {
LOG.info("Authenticating user '{}' using {}", user,
GroupMembershipKeyFilter.class.getSimpleName());
List<String> memberOf = null;
try {
String userDn = ldap.findUserDn(user);
memberOf = ldap.findGroupsForUser(userDn);
LOG.debug("User {} member of : {}", userDn, memberOf);
} catch (NamingException e) {
throw new AuthenticationException("LDAP Authentication failed for user", e);
}
for (String groupDn : memberOf) {
String shortName = LdapUtils.getShortName(groupDn);
if (groupFilter.contains(shortName)) {
LOG.debug("GroupMembershipKeyFilter passes: user '{}' is a member of '{}' group",
user, groupDn);
LOG.info("Authentication succeeded based on group membership");
return;
}
}
LOG.info("Authentication failed based on user membership");
throw new AuthenticationException("Authentication failed: "
+ "User not a member of specified list");
}
}
@VisibleForTesting
static final class UserMembershipKeyFilter implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(UserMembershipKeyFilter.class);
private final Collection<String> groupFilter;
UserMembershipKeyFilter(Collection<String> groupFilter) {
this.groupFilter = groupFilter;
}
@Override
public void apply(DirSearch ldap, String user) throws AuthenticationException {
LOG.info("Authenticating user '{}' using {}", user,
UserMembershipKeyFilter.class.getSimpleName());
List<String> groupDns = new ArrayList<>();
for (String groupId : groupFilter) {
try {
String groupDn = ldap.findGroupDn(groupId);
groupDns.add(groupDn);
} catch (NamingException e) {
LOG.warn("Cannot find DN for group", e);
LOG.debug("Cannot find DN for group " + groupId, e);
}
}
if (groupDns.isEmpty()) {
String msg = String.format("No DN(s) has been found for any of group(s): %s",
Joiner.on(',').join(groupFilter));
LOG.debug(msg);
throw new AuthenticationException("No DN(s) has been found for any of specified group(s)");
}
for (String groupDn : groupDns) {
try {
if (ldap.isUserMemberOfGroup(user, groupDn)) {
LOG.debug("UserMembershipKeyFilter passes: user '{}' is a member of '{}' group",
user, groupDn);
LOG.info("Authentication succeeded based on user membership");
return;
}
} catch (NamingException e) {
LOG.warn("Cannot match user and group", e);
if (LOG.isDebugEnabled()) {
String msg = String.format("Cannot match user '%s' and group '%s'", user, groupDn);
LOG.debug(msg, e);
}
}
}
throw new AuthenticationException(String.format(
"Authentication failed: User '%s' is not a member of listed groups", user));
}
}
}