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}