/** * 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.falcon.security; import org.apache.commons.lang3.StringUtils; import org.apache.falcon.service.ProxyUserService; import org.apache.falcon.service.Services; import org.apache.hadoop.security.UserGroupInformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Current authenticated user via REST. Also captures the proxy user from authorized entity * and doles out proxied UserGroupInformation. Caches proxied users. */ public final class CurrentUser { private static final Logger LOG = LoggerFactory.getLogger(CurrentUser.class); private static final Logger AUDIT = LoggerFactory.getLogger("AUDIT"); private final String authenticatedUser; private String proxyUser; private CurrentUser(String authenticatedUser) { this.authenticatedUser = authenticatedUser; this.proxyUser = authenticatedUser; } private static final ThreadLocal<CurrentUser> CURRENT_USER = new ThreadLocal<CurrentUser>(); /** * Captures the authenticated user. * * @param user authenticated user */ public static void authenticate(final String user) { if (StringUtils.isEmpty(user)) { throw new IllegalStateException("Bad user name sent for authentication"); } LOG.info("Logging in {}", user); CurrentUser currentUser = new CurrentUser(user); CURRENT_USER.set(currentUser); } /** * Proxies doAs user. * * @param doAsUser doAs user * @param proxyHost proxy host * @throws IOException */ public static void proxyDoAsUser(final String doAsUser, final String proxyHost) throws IOException { if (!isAuthenticated()) { throw new IllegalStateException("Authentication not done"); } String currentUser = CURRENT_USER.get().authenticatedUser; if (StringUtils.isNotEmpty(doAsUser) && !doAsUser.equalsIgnoreCase(currentUser)) { if (StringUtils.isEmpty(proxyHost)) { throw new IllegalArgumentException("proxy host cannot be null or empty"); } ProxyUserService proxyUserService = Services.get().getService(ProxyUserService.SERVICE_NAME); try { proxyUserService.validate(currentUser, proxyHost, doAsUser); } catch (IOException ex) { throw new RuntimeException(ex); } CurrentUser user = CURRENT_USER.get(); LOG.info("Authenticated user {} is proxying doAs user {} from host {}", user.authenticatedUser, doAsUser, proxyHost); AUDIT.info("Authenticated user {} is proxying doAs user {} from host {}", user.authenticatedUser, doAsUser, proxyHost); user.proxyUser = doAsUser; } } /** * Captures the entity owner if authenticated user is a super user. * * @param aclOwner entity acl owner * @param aclGroup entity acl group * @throws IOException */ public static void proxy(final String aclOwner, final String aclGroup) throws IOException { if (!isAuthenticated() || StringUtils.isEmpty(aclOwner)) { throw new IllegalStateException("Authentication not done or Bad user name"); } CurrentUser user = CURRENT_USER.get(); LOG.info("Authenticated user {} is proxying entity owner {}/{}", user.authenticatedUser, aclOwner, aclGroup); AUDIT.info("Authenticated user {} is proxying entity owner {}/{}", user.authenticatedUser, aclOwner, aclGroup); user.proxyUser = aclOwner; } /** * Clears the context. */ public static void clear() { CURRENT_USER.remove(); } /** * Checks if the authenticate method is already called. * * @return true if authenticated user is set else false */ public static boolean isAuthenticated() { CurrentUser user = CURRENT_USER.get(); return user != null && user.authenticatedUser != null; } /** * Returns authenticated user. * * @return logged in authenticated user. */ public static String getAuthenticatedUser() { CurrentUser user = CURRENT_USER.get(); if (user == null || user.authenticatedUser == null) { throw new IllegalStateException("No user logged into the system"); } else { return user.authenticatedUser; } } /** * Dole out a UGI object for the current authenticated user if authenticated * else return current user. * * @return UGI object * @throws java.io.IOException */ public static UserGroupInformation getAuthenticatedUGI() throws IOException { return CurrentUser.isAuthenticated() ? createProxyUGI(getAuthenticatedUser()) : UserGroupInformation.getCurrentUser(); } /** * Returns the proxy user. * * @return proxy user */ public static String getUser() { CurrentUser user = CURRENT_USER.get(); if (user == null || user.proxyUser == null) { throw new IllegalStateException("No user logged into the system"); } else { return user.proxyUser; } } private static ConcurrentMap<String, UserGroupInformation> userUgiMap = new ConcurrentHashMap<String, UserGroupInformation>(); /** * Create a proxy UGI object for the proxy user. * * @param proxyUser logged in user * @return UGI object * @throws IOException */ public static UserGroupInformation createProxyUGI(String proxyUser) throws IOException { UserGroupInformation proxyUgi = userUgiMap.get(proxyUser); if (proxyUgi == null) { // taking care of a race condition, the latest UGI will be discarded proxyUgi = UserGroupInformation.createProxyUser( proxyUser, UserGroupInformation.getLoginUser()); userUgiMap.putIfAbsent(proxyUser, proxyUgi); } return proxyUgi; } /** * Dole out a proxy UGI object for the current authenticated user if authenticated * else return current user. * * @return UGI object * @throws java.io.IOException */ public static UserGroupInformation getProxyUGI() throws IOException { return CurrentUser.isAuthenticated() ? createProxyUGI(getUser()) : UserGroupInformation.getCurrentUser(); } /** * Gets a collection of group names the proxy user belongs to. * * @return group names * @throws IOException */ public static Set<String> getGroupNames() throws IOException { HashSet<String> s = new HashSet<String>(Arrays.asList(getProxyUGI().getGroupNames())); return Collections.unmodifiableSet(s); } /** * Returns the primary group name for the proxy user. * * @return primary group name for the proxy user */ public static String getPrimaryGroupName() { try { String[] groups = getProxyUGI().getGroupNames(); if (groups.length > 0) { return groups[0]; } } catch (IOException ignore) { // ignored } return "unknown"; // this can only happen in tests } }