1 /* 2 * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * - Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * - Neither the name of Oracle nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * This source code is provided to illustrate the usage of a given feature 34 * or technique and has been deliberately simplified. Additional steps 35 * required for a production-quality application, such as security checks, 36 * input validation and proper error handling, might not be present in 37 * this sample code. 38 */ 39 40 41 import java.nio.file.*; 42 import java.nio.file.attribute.*; 43 import java.io.IOException; 44 import java.util.*; 45 import java.util.regex.Pattern; 46 47 /** 48 * Sample utility for editing a file's ACL. 49 */ 50 51 public class AclEdit { 52 53 // parse string as list of ACE permissions separated by / parsePermissions(String permsString)54 static Set<AclEntryPermission> parsePermissions(String permsString) { 55 Set<AclEntryPermission> perms = new HashSet<AclEntryPermission>(); 56 String[] result = permsString.split("/"); 57 for (String s : result) { 58 if (s.equals("")) 59 continue; 60 try { 61 perms.add(AclEntryPermission.valueOf(s.toUpperCase())); 62 } catch (IllegalArgumentException x) { 63 System.err.format("Invalid permission '%s'\n", s); 64 System.exit(-1); 65 } 66 } 67 return perms; 68 } 69 70 // parse string as list of ACE flags separated by / parseFlags(String flagsString)71 static Set<AclEntryFlag> parseFlags(String flagsString) { 72 Set<AclEntryFlag> flags = new HashSet<AclEntryFlag>(); 73 String[] result = flagsString.split("/"); 74 for (String s : result) { 75 if (s.equals("")) 76 continue; 77 try { 78 flags.add(AclEntryFlag.valueOf(s.toUpperCase())); 79 } catch (IllegalArgumentException x) { 80 System.err.format("Invalid flag '%s'\n", s); 81 System.exit(-1); 82 } 83 } 84 return flags; 85 } 86 87 // parse ACE type parseType(String typeString)88 static AclEntryType parseType(String typeString) { 89 // FIXME: support audit and alarm types in the future 90 if (typeString.equalsIgnoreCase("allow")) 91 return AclEntryType.ALLOW; 92 if (typeString.equalsIgnoreCase("deny")) 93 return AclEntryType.DENY; 94 System.err.format("Invalid type '%s'\n", typeString); 95 System.exit(-1); 96 return null; // keep compiler happy 97 } 98 99 /** 100 * Parse string of the form: 101 * [user|group:]<username|groupname>:<perms>[:flags]:<allow|deny> 102 */ parseAceString(String s, UserPrincipalLookupService lookupService)103 static AclEntry parseAceString(String s, 104 UserPrincipalLookupService lookupService) 105 { 106 String[] result = s.split(":"); 107 108 // must have at least 3 components (username:perms:type) 109 if (result.length < 3) 110 usage(); 111 112 int index = 0; 113 int remaining = result.length; 114 115 // optional first component can indicate user or group type 116 boolean isGroup = false; 117 if (result[index].equalsIgnoreCase("user") || 118 result[index].equalsIgnoreCase("group")) 119 { 120 if (--remaining < 3) 121 usage(); 122 isGroup = result[index++].equalsIgnoreCase("group"); 123 } 124 125 // user and permissions required 126 String userString = result[index++]; remaining--; 127 String permsString = result[index++]; remaining--; 128 129 // flags are optional 130 String flagsString = ""; 131 String typeString = null; 132 if (remaining == 1) { 133 typeString = result[index++]; 134 } else { 135 if (remaining == 2) { 136 flagsString = result[index++]; 137 typeString = result[index++]; 138 } else { 139 usage(); 140 } 141 } 142 143 // lookup UserPrincipal 144 UserPrincipal user = null; 145 try { 146 user = (isGroup) ? 147 lookupService.lookupPrincipalByGroupName(userString) : 148 lookupService.lookupPrincipalByName(userString); 149 } catch (UserPrincipalNotFoundException x) { 150 System.err.format("Invalid %s '%s'\n", 151 ((isGroup) ? "group" : "user"), 152 userString); 153 System.exit(-1); 154 } catch (IOException x) { 155 System.err.format("Lookup of '%s' failed: %s\n", userString, x); 156 System.exit(-1); 157 } 158 159 // map string representation of permissions, flags, and type 160 Set<AclEntryPermission> perms = parsePermissions(permsString); 161 Set<AclEntryFlag> flags = parseFlags(flagsString); 162 AclEntryType type = parseType(typeString); 163 164 // build the ACL entry 165 return AclEntry.newBuilder() 166 .setType(type) 167 .setPrincipal(user) 168 .setPermissions(perms).setFlags(flags).build(); 169 } 170 usage()171 static void usage() { 172 System.err.println("usage: java AclEdit [ACL-operation] file"); 173 System.err.println(""); 174 System.err.println("Example 1: Prepends access control entry to the begining of the myfile's ACL"); 175 System.err.println(" java AclEdit A+alice:read_data/read_attributes:allow myfile"); 176 System.err.println(""); 177 System.err.println("Example 2: Remove the entry at index 6 of myfile's ACL"); 178 System.err.println(" java AclEdit A6- myfile"); 179 System.err.println(""); 180 System.err.println("Example 3: Replace the entry at index 2 of myfile's ACL"); 181 System.err.println(" java AclEdit A2=bob:write_data/append_data:deny myfile"); 182 System.exit(-1); 183 } 184 185 static enum Action { 186 PRINT, 187 ADD, 188 REMOVE, 189 REPLACE; 190 } 191 192 /** 193 * Main class: parses arguments and prints or edits ACL 194 */ main(String[] args)195 public static void main(String[] args) throws IOException { 196 Action action = null; 197 int index = -1; 198 String entryString = null; 199 200 // parse arguments 201 if (args.length < 1 || args[0].equals("-help") || args[0].equals("-?")) 202 usage(); 203 204 if (args.length == 1) { 205 action = Action.PRINT; 206 } else { 207 String s = args[0]; 208 209 // A[index]+entry 210 if (Pattern.matches("^A[0-9]*\\+.*", s)) { 211 String[] result = s.split("\\+", 2); 212 if (result.length == 2) { 213 if (result[0].length() < 2) { 214 index = 0; 215 } else { 216 index = Integer.parseInt(result[0].substring(1)); 217 } 218 entryString = result[1]; 219 action = Action.ADD; 220 } 221 } 222 223 // Aindex- 224 if (Pattern.matches("^A[0-9]+\\-", s)) { 225 String[] result = s.split("\\-", 2); 226 if (result.length == 2) { 227 index = Integer.parseInt(result[0].substring(1)); 228 entryString = result[1]; 229 action = Action.REMOVE; 230 } 231 } 232 233 // Aindex=entry 234 if (Pattern.matches("^A[0-9]+=.*", s)) { 235 String[] result = s.split("=", 2); 236 if (result.length == 2) { 237 index = Integer.parseInt(result[0].substring(1)); 238 entryString = result[1]; 239 action = Action.REPLACE; 240 } 241 } 242 } 243 if (action == null) 244 usage(); 245 246 int fileArg = (action == Action.PRINT) ? 0 : 1; 247 Path file = Paths.get(args[fileArg]); 248 249 // read file's ACL 250 AclFileAttributeView view = 251 Files.getFileAttributeView(file, AclFileAttributeView.class); 252 if (view == null) { 253 System.err.println("ACLs not supported on this platform"); 254 System.exit(-1); 255 } 256 List<AclEntry> acl = view.getAcl(); 257 258 switch (action) { 259 // print ACL 260 case PRINT : { 261 for (int i=0; i<acl.size(); i++) { 262 System.out.format("%5d: %s\n", i, acl.get(i)); 263 } 264 break; 265 } 266 267 // add ACE to existing ACL 268 case ADD: { 269 AclEntry entry = parseAceString(entryString, file 270 .getFileSystem().getUserPrincipalLookupService()); 271 if (index >= acl.size()) { 272 acl.add(entry); 273 } else { 274 acl.add(index, entry); 275 } 276 view.setAcl(acl); 277 break; 278 } 279 280 // remove ACE 281 case REMOVE: { 282 if (index >= acl.size()) { 283 System.err.format("Index '%d' is invalid", index); 284 System.exit(-1); 285 } 286 acl.remove(index); 287 view.setAcl(acl); 288 break; 289 } 290 291 // replace ACE 292 case REPLACE: { 293 if (index >= acl.size()) { 294 System.err.format("Index '%d' is invalid", index); 295 System.exit(-1); 296 } 297 AclEntry entry = parseAceString(entryString, file 298 .getFileSystem().getUserPrincipalLookupService()); 299 acl.set(index, entry); 300 view.setAcl(acl); 301 break; 302 } 303 } 304 } 305 } 306