Author: dhruba Date: Tue Dec 4 10:51:19 2007 New Revision: 601038 URL: http://svn.apache.org/viewvc?rev=601038&view=rev Log: HADOOP-2299. Defination of a login interface. A simple implementation for Unix users and groups. (Hairong Kuang via dhruba)
Added: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/ lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java (with props) lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java (with props) lucene/hadoop/trunk/src/test/org/apache/hadoop/security/ lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java (with props) Modified: lucene/hadoop/trunk/CHANGES.txt lucene/hadoop/trunk/src/java/org/apache/hadoop/fs/ShellCommand.java Modified: lucene/hadoop/trunk/CHANGES.txt URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/CHANGES.txt?rev=601038&r1=601037&r2=601038&view=diff ============================================================================== --- lucene/hadoop/trunk/CHANGES.txt (original) +++ lucene/hadoop/trunk/CHANGES.txt Tue Dec 4 10:51:19 2007 @@ -27,6 +27,9 @@ HADOOP-1857. Ability to run a script when a task fails to capture stack traces. (Amareshwari Sri Ramadasu via ddas) + + HADOOP-2299. Defination of a login interface. A simple implementation for + Unix users and groups. (Hairong Kuang via dhruba) IMPROVEMENTS Modified: lucene/hadoop/trunk/src/java/org/apache/hadoop/fs/ShellCommand.java URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/java/org/apache/hadoop/fs/ShellCommand.java?rev=601038&r1=601037&r2=601038&view=diff ============================================================================== --- lucene/hadoop/trunk/src/java/org/apache/hadoop/fs/ShellCommand.java (original) +++ lucene/hadoop/trunk/src/java/org/apache/hadoop/fs/ShellCommand.java Tue Dec 4 10:51:19 2007 @@ -23,7 +23,9 @@ /** A base class for running a unix command like du or df*/ abstract public class ShellCommand { - /** a Unix command to get a list of groups */ + /** a Unix command to get the current user's name */ + public final static String USER_NAME_COMMAND = "whoami"; + /** a Unix command to get the current user's groups list */ public static final String GROUPS_COMMAND = "groups"; /** a Unix command to set permission */ public static final String SET_PERMISSION_COMMAND = "chmod"; Added: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java?rev=601038&view=auto ============================================================================== --- lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java (added) +++ lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java Tue Dec 4 10:51:19 2007 @@ -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(); + } +} Propchange: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UnixUserGroupInformation.java ------------------------------------------------------------------------------ svn:keywords = Id Revision HeadURL Added: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java?rev=601038&view=auto ============================================================================== --- lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java (added) +++ lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java Tue Dec 4 10:51:19 2007 @@ -0,0 +1,42 @@ +/** + * 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 org.apache.hadoop.io.Writable; + +/** A [EMAIL PROTECTED] Writable} interface for storing user and groups information. + */ +public interface UserGroupInformation extends Writable { + /** Get username + * + * @return the user's name + */ + public String getUserName(); + + /** Get the name of the groups that the user belong to + * + * @return an array of group names + */ + public String[] getGroupNames(); + + /** Get the default group name. + * + * @return the default the group name + */ + public String getDefaultGroupName(); +} Propchange: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: lucene/hadoop/trunk/src/java/org/apache/hadoop/security/UserGroupInformation.java ------------------------------------------------------------------------------ svn:keywords = Id Revision HeadURL Added: lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java?rev=601038&view=auto ============================================================================== --- lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java (added) +++ lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java Tue Dec 4 10:51:19 2007 @@ -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.hadoop.security; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.TestWritable; + +import junit.framework.TestCase; + +/** Unit tests for UnixUserGroupInformation */ +public class TestUnixUserGroupInformation extends TestCase { + final private static String USER_NAME = "user1"; + final private static String GROUP1_NAME = "group1"; + final private static String GROUP2_NAME = "group2"; + final private static String GROUP3_NAME = "group3"; + final private static String[] GROUP_NAMES = + new String[]{GROUP1_NAME, GROUP2_NAME, GROUP3_NAME}; + + /** Test login method */ + public void testLogin() throws Exception { + Configuration conf = new Configuration(); + + // loin from unix + String userName = UnixUserGroupInformation.getUnixUserName(); + UnixUserGroupInformation curUserGroupInfo = + UnixUserGroupInformation.login(conf); + assertEquals(curUserGroupInfo.getUserName(), userName); + assertTrue(curUserGroupInfo == UnixUserGroupInformation.login(conf)); + + // login from the configuration + UnixUserGroupInformation userGroupInfo = new UnixUserGroupInformation( + USER_NAME, GROUP_NAMES ); + UnixUserGroupInformation.saveToConf(conf, + UnixUserGroupInformation.UGI_PROPERTY_NAME, userGroupInfo); + curUserGroupInfo = UnixUserGroupInformation.login(conf); + assertEquals(curUserGroupInfo, userGroupInfo); + assertTrue(curUserGroupInfo == UnixUserGroupInformation.login(conf)); + } + + /** test constructor */ + public void testConstructor() throws Exception { + UnixUserGroupInformation uugi = + new UnixUserGroupInformation(USER_NAME, GROUP_NAMES); + assertEquals(uugi, new UnixUserGroupInformation( new String[]{ + USER_NAME, GROUP1_NAME, GROUP2_NAME, GROUP3_NAME} )); + // failure test + testConstructorFailures(null, GROUP_NAMES); + testConstructorFailures("", GROUP_NAMES); + testConstructorFailures(USER_NAME, null); + testConstructorFailures(USER_NAME, new String[0]); + testConstructorFailures(USER_NAME, new String[]{null}); + testConstructorFailures(USER_NAME, new String[]{""}); + testConstructorFailures(USER_NAME, new String[]{GROUP1_NAME, null}); + testConstructorFailures(USER_NAME, + new String[]{GROUP1_NAME, null, GROUP2_NAME}); + } + + private void testConstructorFailures(String userName, String[] groupNames) { + boolean gotException = false; + try { + new UnixUserGroupInformation(userName, groupNames); + } catch (Exception e) { + gotException = true; + } + assertTrue(gotException); + } + + public void testEquals() throws Exception { + UnixUserGroupInformation uugi = + new UnixUserGroupInformation(USER_NAME, GROUP_NAMES); + + assertEquals(uugi, uugi); + assertEquals(uugi, new UnixUserGroupInformation(USER_NAME, GROUP_NAMES)); + assertEquals(uugi, new UnixUserGroupInformation(USER_NAME, + new String[]{GROUP1_NAME, GROUP3_NAME, GROUP2_NAME})); + assertFalse(uugi.equals(new UnixUserGroupInformation())); + assertFalse(uugi.equals(new UnixUserGroupInformation(USER_NAME, + new String[]{GROUP2_NAME, GROUP3_NAME, GROUP1_NAME}))); + } + + /** test Writable */ + public void testWritable() throws Exception { + UnixUserGroupInformation ugi = new UnixUserGroupInformation( + USER_NAME, GROUP_NAMES); + TestWritable.testWritable(ugi, new Configuration()); + } +} \ No newline at end of file Propchange: lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: lucene/hadoop/trunk/src/test/org/apache/hadoop/security/TestUnixUserGroupInformation.java ------------------------------------------------------------------------------ svn:keywords = Id Revision HeadURL