View Javadoc
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 /
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 /
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
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      */
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 
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      */
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 }