|
@@ -0,0 +1,388 @@
|
|
|
+/**
|
|
|
+ * 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.hadoop.security;
|
|
|
+
|
|
|
+import java.io.DataInput;
|
|
|
+import java.io.DataOutput;
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.StringTokenizer;
|
|
|
+import java.util.TreeSet;
|
|
|
+
|
|
|
+import javax.security.auth.login.LoginException;
|
|
|
+
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
+import org.apache.hadoop.fs.ShellCommand;
|
|
|
+import org.apache.hadoop.io.Text;
|
|
|
+import org.apache.hadoop.io.WritableUtils;
|
|
|
+
|
|
|
+/** An implementation of UserGroupInformation in the Unix system */
|
|
|
+public class UnixUserGroupInformation implements UserGroupInformation {
|
|
|
+ final static public String UGI_PROPERTY_NAME = "hadoop.job.ugi";
|
|
|
+ final static private HashMap<String, UnixUserGroupInformation> user2UGIMap =
|
|
|
+ new HashMap<String, UnixUserGroupInformation>();
|
|
|
+
|
|
|
+ private String userName;
|
|
|
+ private String[] groupNames;
|
|
|
+
|
|
|
+ /** Default constructor
|
|
|
+ */
|
|
|
+ public UnixUserGroupInformation() {
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Constructor with parameters user name and its group names.
|
|
|
+ * The first entry in the groups list is the default group.
|
|
|
+ *
|
|
|
+ * @param userName a user's name
|
|
|
+ * @param groupNames groups list, first of which is the default group
|
|
|
+ * @exception IllegalArgumentException if any argument is null
|
|
|
+ */
|
|
|
+ UnixUserGroupInformation(String userName, String[] groupNames)
|
|
|
+ throws IOException {
|
|
|
+ setUserGroupNames(userName, groupNames);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Constructor with parameter user/group names
|
|
|
+ *
|
|
|
+ * @param ugi an array containing user/group names, the first
|
|
|
+ * element of which is the user name, the second of
|
|
|
+ * which is the default group name.
|
|
|
+ * @exception IllegalArgumentException if the array size is less than 2
|
|
|
+ * or any element is null.
|
|
|
+ */
|
|
|
+ UnixUserGroupInformation(String[] ugi) {
|
|
|
+ if (ugi==null || ugi.length < 2) {
|
|
|
+ throw new IllegalArgumentException( "Parameter does contain at least "+
|
|
|
+ "one user name and one group name");
|
|
|
+ }
|
|
|
+ String[] groupNames = new String[ugi.length-1];
|
|
|
+ System.arraycopy(ugi, 1, groupNames, 0, groupNames.length);
|
|
|
+ setUserGroupNames(ugi[0], groupNames);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set this object's user name and group names
|
|
|
+ *
|
|
|
+ * @param userName a user's name
|
|
|
+ * @param groupNames groups list, the first of which is the default group
|
|
|
+ * @exception IllegalArgumentException if any argument is null
|
|
|
+ */
|
|
|
+ private void setUserGroupNames(String userName, String[] groupNames) {
|
|
|
+ if (userName==null || userName.length()==0 ||
|
|
|
+ groupNames== null || groupNames.length==0) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "Parameters should not be null or an empty string/array");
|
|
|
+ }
|
|
|
+ for (int i=0; i<groupNames.length; i++) {
|
|
|
+ if(groupNames[i] == null || groupNames[i].length() == 0) {
|
|
|
+ throw new IllegalArgumentException("A null group name at index " + i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.userName = userName;
|
|
|
+ this.groupNames = groupNames;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Return an array of group names
|
|
|
+ */
|
|
|
+ public String[] getGroupNames() {
|
|
|
+ return groupNames;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Return the user's name
|
|
|
+ */
|
|
|
+ public String getUserName() {
|
|
|
+ return userName;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Return the default group name
|
|
|
+ */
|
|
|
+ public String getDefaultGroupName() {
|
|
|
+ return groupNames[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The following two methods implements Writable interface */
|
|
|
+ final private static String UGI_TECHNOLOGY = "STRING_UGI";
|
|
|
+ /** Deserialize this object
|
|
|
+ * First check if this is a UGI in the string format.
|
|
|
+ * If no, throw an IOException; otherwise
|
|
|
+ * set this object's fields by reading them from the given data input
|
|
|
+ *
|
|
|
+ * @param in input stream
|
|
|
+ * @exception IOException is thrown if encounter any error when reading
|
|
|
+ */
|
|
|
+ public void readFields(DataInput in) throws IOException {
|
|
|
+ // read UGI type first
|
|
|
+ String ugiType = Text.readString(in);
|
|
|
+ if (!UGI_TECHNOLOGY.equals(ugiType)) {
|
|
|
+ throw new IOException("Expect UGI prefix: " + UGI_TECHNOLOGY +
|
|
|
+ ", but receive a prefix: " + ugiType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // read this object
|
|
|
+ userName = Text.readString(in);
|
|
|
+ int numOfGroups = WritableUtils.readVInt(in);
|
|
|
+ groupNames = new String[numOfGroups];
|
|
|
+ for (int i = 0; i < numOfGroups; i++) {
|
|
|
+ groupNames[i] = Text.readString(in);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Serialize this object
|
|
|
+ * First write a string marking that this is a UGI in the string format,
|
|
|
+ * then write this object's serialized form to the given data output
|
|
|
+ *
|
|
|
+ * @param out output stream
|
|
|
+ * @exception IOException if encounter any error during writing
|
|
|
+ */
|
|
|
+ public void write(DataOutput out) throws IOException {
|
|
|
+ // write a prefix indicating the type of UGI being written
|
|
|
+ Text.writeString(out, UGI_TECHNOLOGY);
|
|
|
+ // write this object
|
|
|
+ Text.writeString(out, userName);
|
|
|
+ WritableUtils.writeVInt(out, groupNames.length);
|
|
|
+ for (String groupName : groupNames) {
|
|
|
+ Text.writeString(out, groupName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The following two methods deal with transferring UGI through conf.
|
|
|
+ * In this pass of implementation we store UGI as a string in conf.
|
|
|
+ * Later we may change it to be a more general approach that stores
|
|
|
+ * it as a byte array */
|
|
|
+ /** Store the given <code>ugi</code> as a comma separated string in
|
|
|
+ * <code>conf</code> as a property <code>attr</code>
|
|
|
+ *
|
|
|
+ * The String starts with the user name followed by the default group names,
|
|
|
+ * and other group names.
|
|
|
+ *
|
|
|
+ * @param conf configuration
|
|
|
+ * @param attr property name
|
|
|
+ * @param ugi a UnixUserGroupInformation
|
|
|
+ */
|
|
|
+ public static void saveToConf(Configuration conf, String attr,
|
|
|
+ UnixUserGroupInformation ugi ) {
|
|
|
+ conf.set(attr, ugi.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Read a UGI from the given <code>conf</code>
|
|
|
+ *
|
|
|
+ * The object is expected to store with the property name <code>attr</code>
|
|
|
+ * as a comma separated string that starts
|
|
|
+ * with the user name followed by group names.
|
|
|
+ * If the property name is not defined, return null.
|
|
|
+ * It's assumed that there is only one UGI per user. If this user already
|
|
|
+ * has a UGI in the ugi map, return the ugi in the map.
|
|
|
+ * Otherwise, construct a UGI from the configuration, store it in the
|
|
|
+ * ugi map and return it.
|
|
|
+ *
|
|
|
+ * @param conf configuration
|
|
|
+ * @param attr property name
|
|
|
+ * @return a UnixUGI
|
|
|
+ * @throws LoginException if the stored string is ill-formatted.
|
|
|
+ */
|
|
|
+ public static UnixUserGroupInformation readFromConf(
|
|
|
+ Configuration conf, String attr) throws LoginException {
|
|
|
+ String[] ugi = conf.getStrings(attr);
|
|
|
+ if(ugi == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ UnixUserGroupInformation currentUGI = null;
|
|
|
+ if (ugi.length>0 ){
|
|
|
+ currentUGI = user2UGIMap.get(ugi[0]);
|
|
|
+ }
|
|
|
+ if (currentUGI == null) {
|
|
|
+ try {
|
|
|
+ currentUGI = new UnixUserGroupInformation(ugi);
|
|
|
+ user2UGIMap.put(currentUGI.getUserName(), currentUGI);
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
+ throw new LoginException("Login failed: "+e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return currentUGI;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Get current user's name and the names of all its groups from Unix.
|
|
|
+ * It's assumed that there is only one UGI per user. If this user already
|
|
|
+ * has a UGI in the ugi map, return the ugi in the map.
|
|
|
+ * Otherwise get the current user's information from Unix, store it
|
|
|
+ * in the map, and return it.
|
|
|
+ */
|
|
|
+ private static UnixUserGroupInformation login() throws LoginException {
|
|
|
+ try {
|
|
|
+ String userName = getUnixUserName();
|
|
|
+
|
|
|
+ // check if this user already has a UGI object in the ugi map
|
|
|
+ UnixUserGroupInformation ugi = user2UGIMap.get(userName);
|
|
|
+ if (ugi != null) {
|
|
|
+ return ugi;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* get groups list from UNIX.
|
|
|
+ * It's assumed that the first group is the default group.
|
|
|
+ */
|
|
|
+ String[] groupNames = getUnixGroups();
|
|
|
+
|
|
|
+ // construct a Unix UGI
|
|
|
+ ugi = new UnixUserGroupInformation(userName, groupNames);
|
|
|
+ user2UGIMap.put(ugi.getUserName(), ugi);
|
|
|
+ return ugi;
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new LoginException("Login failed: "+e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Get a user's name & its group names from the given configuration;
|
|
|
+ * If it is not defined in the configuration, get the current user's
|
|
|
+ * information from Unix.
|
|
|
+ * If the user has a UGI in the ugi map, return the one in
|
|
|
+ * the UGI map.
|
|
|
+ *
|
|
|
+ * @param conf either a job configuration or client's configuration
|
|
|
+ * @return UnixUserGroupInformation a user/group information
|
|
|
+ * @exception LoginException if not able to get the user/group information
|
|
|
+ */
|
|
|
+ public static UnixUserGroupInformation login(Configuration conf)
|
|
|
+ throws LoginException {
|
|
|
+ UnixUserGroupInformation ugi = readFromConf(conf, UGI_PROPERTY_NAME);
|
|
|
+ if (ugi == null) {
|
|
|
+ ugi = login();
|
|
|
+ }
|
|
|
+ return ugi;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Return a string representation of a string array.
|
|
|
+ * Two strings are separated by a blank.
|
|
|
+ */
|
|
|
+ private static String toString(String[] strArray) {
|
|
|
+ if (strArray==null || strArray.length==0) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ StringBuilder buf = new StringBuilder(strArray[0]);
|
|
|
+ for (int i=1; i<strArray.length; i++) {
|
|
|
+ buf.append(' ');
|
|
|
+ buf.append(strArray[i]);
|
|
|
+ }
|
|
|
+ return buf.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Get current user's name from Unix by running the command whoami.
|
|
|
+ *
|
|
|
+ * @return current user's name
|
|
|
+ * @throws IOException if encounter any error while running the command
|
|
|
+ */
|
|
|
+ static String getUnixUserName() throws IOException {
|
|
|
+ String[] result = executeShellCommand(
|
|
|
+ new String[]{ShellCommand.USER_NAME_COMMAND});
|
|
|
+ if (result.length!=1) {
|
|
|
+ throw new IOException("Expect one token as the result of " +
|
|
|
+ ShellCommand.USER_NAME_COMMAND + ": " + toString(result));
|
|
|
+ }
|
|
|
+ return result[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Get the current user's group list from Unix by running the command groups
|
|
|
+ *
|
|
|
+ * @return the groups list that the current user belongs to
|
|
|
+ * @throws IOException if encounter any error when running the command
|
|
|
+ */
|
|
|
+ private static String[] getUnixGroups() throws IOException {
|
|
|
+ return executeShellCommand(new String[]{ShellCommand.GROUPS_COMMAND});
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Execute a command and return the result as an array of Strings */
|
|
|
+ private static String[] executeShellCommand(String[] command)
|
|
|
+ throws IOException {
|
|
|
+ String groups = ShellCommand.execCommand(command);
|
|
|
+ StringTokenizer tokenizer = new StringTokenizer(groups);
|
|
|
+ int numOfTokens = tokenizer.countTokens();
|
|
|
+ String[] tokens = new String[numOfTokens];
|
|
|
+ for (int i=0; tokenizer.hasMoreTokens(); i++) {
|
|
|
+ tokens[i] = tokenizer.nextToken();
|
|
|
+ }
|
|
|
+
|
|
|
+ return tokens;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Decide if two UGIs are the same
|
|
|
+ *
|
|
|
+ * @param other other object
|
|
|
+ * @return true if they are the same; false otherwise.
|
|
|
+ */
|
|
|
+ public boolean equals(Object other) {
|
|
|
+ if (this == other) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(other instanceof UnixUserGroupInformation)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ UnixUserGroupInformation otherUGI = (UnixUserGroupInformation)other;
|
|
|
+
|
|
|
+ // check userName
|
|
|
+ if (userName == null) {
|
|
|
+ if (otherUGI.getUserName() != null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!userName.equals(otherUGI.getUserName())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // checkGroupNames
|
|
|
+ if (groupNames == otherUGI.groupNames) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (groupNames.length != otherUGI.groupNames.length) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // check default group name
|
|
|
+ if (groupNames.length>0 && !groupNames[0].equals(otherUGI.groupNames[0])) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // check all group names, ignoring the order
|
|
|
+ return new TreeSet<String>(Arrays.asList(groupNames)).equals(
|
|
|
+ new TreeSet<String>(Arrays.asList(otherUGI.groupNames)));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Returns a hash code for this UGI.
|
|
|
+ * The hash code for a UGI is the hash code of its user name string.
|
|
|
+ *
|
|
|
+ * @return a hash code value for this UGI.
|
|
|
+ */
|
|
|
+ public int hashCode() {
|
|
|
+ return getUserName().hashCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Convert this object to a string
|
|
|
+ *
|
|
|
+ * @return a comma separated string containing the user name and group names
|
|
|
+ */
|
|
|
+ public String toString() {
|
|
|
+ StringBuilder buf = new StringBuilder();
|
|
|
+ buf.append(userName);
|
|
|
+ for (String groupName : groupNames) {
|
|
|
+ buf.append(',');
|
|
|
+ buf.append(groupName);
|
|
|
+ }
|
|
|
+ return buf.toString();
|
|
|
+ }
|
|
|
+}
|