001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.security.authorize;
019
020import java.io.DataInput;
021import java.io.DataOutput;
022import java.io.IOException;
023import java.util.Collection;
024import java.util.HashSet;
025import java.util.LinkedList;
026import java.util.List;
027
028import org.apache.hadoop.classification.InterfaceAudience;
029import org.apache.hadoop.classification.InterfaceStability;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.io.Text;
032import org.apache.hadoop.io.Writable;
033import org.apache.hadoop.io.WritableFactories;
034import org.apache.hadoop.io.WritableFactory;
035import org.apache.hadoop.security.Groups;
036import org.apache.hadoop.security.UserGroupInformation;
037import org.apache.hadoop.util.StringUtils;
038
039/**
040 * Class representing a configured access control list.
041 */
042@InterfaceAudience.Public
043@InterfaceStability.Evolving
044public class AccessControlList implements Writable {
045
046  static {                                      // register a ctor
047    WritableFactories.setFactory
048    (AccessControlList.class,
049      new WritableFactory() {
050        @Override
051        public Writable newInstance() { return new AccessControlList(); }
052      });
053  }
054
055  // Indicates an ACL string that represents access to all users
056  public static final String WILDCARD_ACL_VALUE = "*";
057  private static final int INITIAL_CAPACITY = 256;
058
059  // Set of users who are granted access.
060  private Collection<String> users;
061  // Set of groups which are granted access
062  private Collection<String> groups;
063  // Whether all users are granted access.
064  private boolean allAllowed;
065
066  private Groups groupsMapping = Groups.getUserToGroupsMappingService(new Configuration());
067
068  /**
069   * This constructor exists primarily for AccessControlList to be Writable.
070   */
071  public AccessControlList() {
072  }
073
074  /**
075   * Construct a new ACL from a String representation of the same.
076   * 
077   * The String is a a comma separated list of users and groups.
078   * The user list comes first and is separated by a space followed 
079   * by the group list. For e.g. "user1,user2 group1,group2"
080   * 
081   * @param aclString String representation of the ACL
082   */
083  public AccessControlList(String aclString) {
084    buildACL(aclString.split(" ", 2));
085  }
086  
087  /**
088   * Construct a new ACL from String representation of users and groups
089   * 
090   * The arguments are comma separated lists
091   * 
092   * @param users comma separated list of users
093   * @param groups comma separated list of groups
094   */
095  public AccessControlList(String users, String groups) {
096    buildACL(new String[] {users, groups});
097  }
098
099  /**
100   * Build ACL from the given two Strings.
101   * The Strings contain comma separated values.
102   *
103   * @param aclString build ACL from array of Strings
104   */
105  private void buildACL(String[] userGroupStrings) {
106    users = new HashSet<String>();
107    groups = new HashSet<String>();
108    for (String aclPart : userGroupStrings) {
109      if (aclPart != null && isWildCardACLValue(aclPart)) {
110        allAllowed = true;
111        break;
112      }
113    }
114    if (!allAllowed) {      
115      if (userGroupStrings.length >= 1 && userGroupStrings[0] != null) {
116        users = StringUtils.getTrimmedStringCollection(userGroupStrings[0]);
117      } 
118      
119      if (userGroupStrings.length == 2 && userGroupStrings[1] != null) {
120        groups = StringUtils.getTrimmedStringCollection(userGroupStrings[1]);
121        groupsMapping.cacheGroupsAdd(new LinkedList<String>(groups));
122      }
123    }
124  }
125  
126  /**
127   * Checks whether ACL string contains wildcard
128   *
129   * @param aclString check this ACL string for wildcard
130   * @return true if ACL string contains wildcard false otherwise
131   */
132  private boolean isWildCardACLValue(String aclString) {
133    if (aclString.contains(WILDCARD_ACL_VALUE) && 
134        aclString.trim().equals(WILDCARD_ACL_VALUE)) {
135      return true;
136    }
137    return false;
138  }
139
140  public boolean isAllAllowed() {
141    return allAllowed;
142  }
143  
144  /**
145   * Add user to the names of users allowed for this service.
146   * 
147   * @param user
148   *          The user name
149   */
150  public void addUser(String user) {
151    if (isWildCardACLValue(user)) {
152      throw new IllegalArgumentException("User " + user + " can not be added");
153    }
154    if (!isAllAllowed()) {
155      users.add(user);
156    }
157  }
158
159  /**
160   * Add group to the names of groups allowed for this service.
161   * 
162   * @param group
163   *          The group name
164   */
165  public void addGroup(String group) {
166    if (isWildCardACLValue(group)) {
167      throw new IllegalArgumentException("Group " + group + " can not be added");
168    }
169    if (!isAllAllowed()) {
170      List<String> groupsList = new LinkedList<String>();
171      groupsList.add(group);
172      groupsMapping.cacheGroupsAdd(groupsList);
173      groups.add(group);
174    }
175  }
176
177  /**
178   * Remove user from the names of users allowed for this service.
179   * 
180   * @param user
181   *          The user name
182   */
183  public void removeUser(String user) {
184    if (isWildCardACLValue(user)) {
185      throw new IllegalArgumentException("User " + user + " can not be removed");
186    }
187    if (!isAllAllowed()) {
188      users.remove(user);
189    }
190  }
191
192  /**
193   * Remove group from the names of groups allowed for this service.
194   * 
195   * @param group
196   *          The group name
197   */
198  public void removeGroup(String group) {
199    if (isWildCardACLValue(group)) {
200      throw new IllegalArgumentException("Group " + group
201          + " can not be removed");
202    }
203    if (!isAllAllowed()) {
204      groups.remove(group);
205    }
206  }
207
208  /**
209   * Get the names of users allowed for this service.
210   * @return the set of user names. the set must not be modified.
211   */
212  public Collection<String> getUsers() {
213    return users;
214  }
215  
216  /**
217   * Get the names of user groups allowed for this service.
218   * @return the set of group names. the set must not be modified.
219   */
220  public Collection<String> getGroups() {
221    return groups;
222  }
223
224  /**
225   * Checks if a user represented by the provided {@link UserGroupInformation}
226   * is a member of the Access Control List
227   * @param ugi UserGroupInformation to check if contained in the ACL
228   * @return true if ugi is member of the list
229   */
230  public final boolean isUserInList(UserGroupInformation ugi) {
231    if (allAllowed || users.contains(ugi.getShortUserName())) {
232      return true;
233    } else if (!groups.isEmpty()) {
234      for(String group: ugi.getGroupNames()) {
235        if (groups.contains(group)) {
236          return true;
237        }
238      }
239    }
240    return false;
241  }
242
243  public boolean isUserAllowed(UserGroupInformation ugi) {
244    return isUserInList(ugi);
245  }
246
247  /**
248   * Returns descriptive way of users and groups that are part of this ACL.
249   * Use {@link #getAclString()} to get the exact String that can be given to
250   * the constructor of AccessControlList to create a new instance.
251   */
252  @Override
253  public String toString() {
254    String str = null;
255
256    if (allAllowed) {
257      str = "All users are allowed";
258    }
259    else if (users.isEmpty() && groups.isEmpty()) {
260      str = "No users are allowed";
261    }
262    else {
263      String usersStr = null;
264      String groupsStr = null;
265      if (!users.isEmpty()) {
266        usersStr = users.toString();
267      }
268      if (!groups.isEmpty()) {
269        groupsStr = groups.toString();
270      }
271
272      if (!users.isEmpty() && !groups.isEmpty()) {
273        str = "Users " + usersStr + " and members of the groups "
274            + groupsStr + " are allowed";
275      }
276      else if (!users.isEmpty()) {
277        str = "Users " + usersStr + " are allowed";
278      }
279      else {// users is empty array and groups is nonempty
280        str = "Members of the groups "
281            + groupsStr + " are allowed";
282      }
283    }
284
285    return str;
286  }
287
288  /**
289   * Returns the access control list as a String that can be used for building a
290   * new instance by sending it to the constructor of {@link AccessControlList}.
291   */
292  public String getAclString() {
293    StringBuilder sb = new StringBuilder(INITIAL_CAPACITY);
294    if (allAllowed) {
295      sb.append('*');
296    }
297    else {
298      sb.append(getUsersString());
299      sb.append(" ");
300      sb.append(getGroupsString());
301    }
302    return sb.toString();
303  }
304
305  /**
306   * Serializes the AccessControlList object
307   */
308  @Override
309  public void write(DataOutput out) throws IOException {
310    String aclString = getAclString();
311    Text.writeString(out, aclString);
312  }
313
314  /**
315   * Deserializes the AccessControlList object
316   */
317  @Override
318  public void readFields(DataInput in) throws IOException {
319    String aclString = Text.readString(in);
320    buildACL(aclString.split(" ", 2));
321  }
322
323  /**
324   * Returns comma-separated concatenated single String of the set 'users'
325   *
326   * @return comma separated list of users
327   */
328  private String getUsersString() {
329    return getString(users);
330  }
331
332  /**
333   * Returns comma-separated concatenated single String of the set 'groups'
334   *
335   * @return comma separated list of groups
336   */
337  private String getGroupsString() {
338    return getString(groups);
339  }
340
341  /**
342   * Returns comma-separated concatenated single String of all strings of
343   * the given set
344   *
345   * @param strings set of strings to concatenate
346   */
347  private String getString(Collection<String> strings) {
348    StringBuilder sb = new StringBuilder(INITIAL_CAPACITY);
349    boolean first = true;
350    for(String str: strings) {
351      if (!first) {
352        sb.append(",");
353      } else {
354        first = false;
355      }
356      sb.append(str);
357    }
358    return sb.toString();
359  }
360}