blob: b550d8d7817a969ec1d35cb83e907d9e955bda34 [file] [log] [blame]
Christian van Spaandonk63814412008-08-02 09:56:01 +00001/*
2 * $Header: /cvshome/build/info.dmtree/src/info/dmtree/Acl.java,v 1.6 2006/07/12 21:21:37 hargrave Exp $
3 *
4 * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18package info.dmtree;
19
20import java.util.*;
21
22/**
23 * <code>Acl</code> is an immutable class representing structured access to
24 * DMT ACLs. Under OMA DM the ACLs are defined as strings with an internal
25 * syntax.
26 * <p>
27 * The methods of this class taking a principal as parameter accept remote
28 * server IDs (as passed to
29 * {@link DmtAdmin#getSession(String, String, int) DmtAdmin.getSession}), as
30 * well as &quot; <code>*</code> &quot; indicating any principal.
31 * <p>
32 * The syntax for valid remote server IDs:<br>
33 * &lt;<i>server-identifier</i>&gt; ::= All printable characters except
34 * <code>'='</code>, <code>'&amp;'</code>, <code>'*'</code>, <code>'+'</code>
35 * or white-space characters.
36 */
37public final class Acl {
38
39 // ----- Public constants -----//
40
41 /**
42 * Principals holding this permission can issue GET command on the node
43 * having this ACL.
44 */
45 public static final int GET = 1;
46
47 /**
48 * Principals holding this permission can issue ADD commands on the node
49 * having this ACL.
50 */
51 public static final int ADD = 2;
52
53 /**
54 * Principals holding this permission can issue REPLACE commands on the node
55 * having this ACL.
56 */
57 public static final int REPLACE = 4;
58
59 /**
60 * Principals holding this permission can issue DELETE commands on the node
61 * having this ACL.
62 */
63 public static final int DELETE = 8;
64
65 /**
66 * Principals holding this permission can issue EXEC commands on the node
67 * having this ACL.
68 */
69 public static final int EXEC = 16;
70
71 /**
72 * Principals holding this permission can issue any command on the node
73 * having this ACL. This permission is the logical OR of {@link #ADD},
74 * {@link #DELETE}, {@link #EXEC}, {@link #GET} and {@link #REPLACE}
75 * permissions.
76 */
77 public static final int ALL_PERMISSION = ADD | DELETE | EXEC | GET
78 | REPLACE;
79
80 // ----- Private constants -----//
81
82 private static final int[] PERMISSION_CODES = new int[] { ADD, DELETE,
83 EXEC, GET, REPLACE };
84
85 private static final String[] PERMISSION_NAMES = new String[] { "Add",
86 "Delete", "Exec", "Get", "Replace" };
87
88 private static final String ALL_PRINCIPALS = "*";
89
90 // ----- Private fields -----//
91
92 // the implementation takes advantage of this being a sorted map
93 private final TreeMap principalPermissions;
94
95 private final int globalPermissions;
96
97 // ----- Public constructors -----//
98
99 /**
100 * Create an instance of the ACL from its canonic string representation.
101 *
102 * @param acl The string representation of the ACL as defined in OMA DM. If
103 * <code>null</code> or empty then it represents an empty list of
104 * principals with no permissions.
105 * @throws IllegalArgumentException if acl is not a valid OMA DM ACL string
106 */
107 public Acl(String acl) {
108 if (acl == null || acl.equals("")) { // empty permission set
109 principalPermissions = new TreeMap();
110 globalPermissions = 0;
111 return;
112 }
113
114 TreeMap tempPrincipalPermissions = new TreeMap();
115 int tempGlobalPermissions = 0;
116
117 String[] aclEntries = split(acl, '&', -1);
118 for (int i = 0; i < aclEntries.length; i++) {
119 if (aclEntries[i].length() == 0)
120 throw new IllegalArgumentException(
121 "Invalid ACL string: empty ACL entry.");
122
123 String[] entryParts = split(aclEntries[i], '=', 2);
124 if (entryParts.length == 1)
125 throw new IllegalArgumentException(
126 "Invalid ACL string: no '=' in ACL entry.");
127 if (entryParts[1].length() == 0)
128 throw new IllegalArgumentException(
129 "Invalid ACL string: no server identifiers in ACL entry.");
130
131 int command = parseCommand(entryParts[0]);
132 String[] serverIds = split(entryParts[1], '+', -1);
133 for (int j = 0; j < serverIds.length; j++) {
134 if (serverIds[j].length() == 0)
135 throw new IllegalArgumentException(
136 "Invalid ACL string: empty server identifier.");
137
138 if (serverIds[j].equals(ALL_PRINCIPALS))
139 tempGlobalPermissions |= command;
140 else {
141 checkServerId(serverIds[j], "Invalid ACL string: "
142 + "server ID contains illegal character");
143 Integer n = (Integer) tempPrincipalPermissions
144 .get(serverIds[j]);
145 int oldPermission = (n != null) ? n.intValue() : 0;
146 tempPrincipalPermissions.put(serverIds[j], new Integer(
147 oldPermission | command));
148 }
149 }
150 }
151
152 principalPermissions = tempPrincipalPermissions;
153 globalPermissions = tempGlobalPermissions;
154 }
155
156 /**
157 * Creates an instance with a specified list of principals and the
158 * permissions they hold. The two arrays run in parallel, that is
159 * <code>principals[i]</code> will hold <code>permissions[i]</code> in
160 * the ACL.
161 * <p>
162 * A principal name may not appear multiple times in the 'principals'
163 * argument. If the &quot;*&quot; principal appears in the array, the
164 * corresponding permissions will be granted to all principals (regardless
165 * of whether they appear in the array or not).
166 *
167 * @param principals The array of principals
168 * @param permissions The array of permissions
169 * @throws IllegalArgumentException if the length of the two arrays are not
170 * the same, if any array element is invalid, or if a principal
171 * appears multiple times in the <code>principals</code> array
172 */
173 public Acl(String[] principals, int[] permissions) {
174 if (principals.length != permissions.length)
175 throw new IllegalArgumentException(
176 "The lengths of the principal and permission arrays are not the same.");
177
178 TreeMap tempPrincipalPermissions = new TreeMap();
179 int tempGlobalPermissions = 0;
180
181 for (int i = 0; i < principals.length; i++) {
182 // allow one * in 'principals' array, remove after loop
183 if (!ALL_PRINCIPALS.equals(principals[i]))
184 checkPrincipal(principals[i]);
185 checkPermissions(permissions[i]);
186
187 Integer permInt = new Integer(permissions[i]);
188 Object old = tempPrincipalPermissions.put(principals[i], permInt);
189 if (old != null)
190 throw new IllegalArgumentException("Principal '"
191 + principals[i]
192 + "' appears multiple times in the principal array.");
193 }
194
195 // set the global permissions if there was a * in the array
196 Object globalPermObj = tempPrincipalPermissions.remove(ALL_PRINCIPALS);
197 if (globalPermObj != null)
198 tempGlobalPermissions = ((Integer) globalPermObj).intValue();
199
200 principalPermissions = tempPrincipalPermissions;
201 globalPermissions = tempGlobalPermissions;
202 }
203
204 // ----- Private constructors -----//
205
206 /**
207 * Creates an instance identical to the <code>base</code> ACL except for
208 * the permissions of the given <code>principal</code>, which are
209 * overwritten with the given <code>permissions</code>.
210 * <p>
211 * Assumes that the permissions parameter has been checked. All
212 * modifications of an <code>Acl</code> (add, delete, set) are done
213 * through this method.
214 *
215 * @param base The ACL that provides all permissions except for permissions
216 * of the given principal.
217 * @param principal The entity to which permission should be granted.
218 * @param permissions The set of permissions to be given. The parameter can
219 * be a logical <code>or</code> of the permission constants defined
220 * in this class.
221 */
222 private Acl(Acl base, String principal, int permissions) {
223 // make a shallow copy of the permission table, the keys (String) and
224 // values (Integer) are immutable anyway
225 TreeMap tempPrincipalPermissions = (TreeMap) base.principalPermissions
226 .clone();
227 int tempGlobalPermissions = base.globalPermissions;
228
229 int deletedGlobalPerm = tempGlobalPermissions & ~permissions;
230 if (ALL_PRINCIPALS.equals(principal)) {
231 deleteFromAll(tempPrincipalPermissions, deletedGlobalPerm);
232 tempGlobalPermissions = permissions;
233 } else {
234 checkPrincipal(principal);
235
236 if (deletedGlobalPerm != 0)
237 throw new IllegalArgumentException(
238 "Cannot revoke globally set permissions ("
239 + writeCommands(deletedGlobalPerm)
240 + ") from a specific principal (" + principal
241 + ").");
242
243 setPrincipalPermission(tempPrincipalPermissions, principal,
244 permissions);
245 }
246
247 principalPermissions = tempPrincipalPermissions;
248 globalPermissions = tempGlobalPermissions;
249 }
250
251 // ----- Public methods -----//
252
253 /**
254 * Checks whether the given object is equal to this <code>Acl</code>
255 * instance. Two <code>Acl</code> instances are equal if they allow the
256 * same set of permissions for the same set of principals.
257 *
258 * @param obj the object to compare with this <code>Acl</code> instance
259 * @return <code>true</code> if the parameter represents the same ACL as
260 * this instance
261 */
262 public boolean equals(Object obj) {
263 if (obj == this)
264 return true;
265
266 if (!(obj instanceof Acl))
267 return false;
268
269 Acl other = (Acl) obj;
270
271 if (globalPermissions != other.globalPermissions
272 || principalPermissions.size() != other.principalPermissions
273 .size())
274 return false;
275
276 // principalPermissions sets cannot be easily compared, because they are
277 // not canonical: the global permissions may or may not be present for
278 // each principal, without changing the meaning of the Acl object.
279
280 // Compare canonical string representations, inefficient but simple.
281 return toString().equals(other.toString());
282 }
283
284 /**
285 * Returns the hash code for this ACL instance. If two <code>Acl</code>
286 * instances are equal according to the {@link #equals} method, then calling
287 * this method on each of them must produce the same integer result.
288 *
289 * @return hash code for this ACL
290 */
291 public int hashcode() {
292 // Using the hash code of the canonical string representation, because
293 // the principalPermissions set is not canonical (see above).
294 return toString().hashCode();
295 }
296
297 /**
298 * Create a new <code>Acl</code> instance from this <code>Acl</code> with
299 * the given permission added for the given principal. The already existing
300 * permissions of the principal are not affected.
301 *
302 * @param principal The entity to which permissions should be granted, or
303 * &quot;*&quot; to grant permissions to all principals.
304 * @param permissions The permissions to be given. The parameter can be a
305 * logical <code>or</code> of more permission constants defined in
306 * this class.
307 * @return a new <code>Acl</code> instance
308 * @throws IllegalArgumentException if <code>principal</code> is not a
309 * valid principal name or if <code>permissions</code> is not a
310 * valid combination of the permission constants defined in this
311 * class
312 */
313 public synchronized Acl addPermission(String principal, int permissions) {
314 checkPermissions(permissions);
315
316 int oldPermissions = getPermissions(principal);
317 return setPermission(principal, oldPermissions | permissions);
318 }
319
320 /**
321 * Create a new <code>Acl</code> instance from this <code>Acl</code> with
322 * the given permission revoked from the given principal. Other permissions
323 * of the principal are not affected.
324 * <p>
325 * Note, that it is not valid to revoke a permission from a specific
326 * principal if that permission is granted globally to all principals.
327 *
328 * @param principal The entity from which permissions should be revoked, or
329 * &quot;*&quot; to revoke permissions from all principals.
330 * @param permissions The permissions to be revoked. The parameter can be a
331 * logical <code>or</code> of more permission constants defined in
332 * this class.
333 * @return a new <code>Acl</code> instance
334 * @throws IllegalArgumentException if <code>principal</code> is not a
335 * valid principal name, if <code>permissions</code> is not a
336 * valid combination of the permission constants defined in this
337 * class, or if a globally granted permission would have been
338 * revoked from a specific principal
339 */
340 public synchronized Acl deletePermission(String principal, int permissions) {
341 checkPermissions(permissions);
342
343 int oldPermissions = getPermissions(principal);
344 return setPermission(principal, oldPermissions & ~permissions);
345 }
346
347 /**
348 * Get the permissions associated to a given principal.
349 *
350 * @param principal The entity whose permissions to query, or &quot;*&quot;
351 * to query the permissions that are granted globally, to all
352 * principals
353 * @return The permissions of the given principal. The returned
354 * <code>int</code> is a bitmask of the permission constants defined
355 * in this class
356 * @throws IllegalArgumentException if <code>principal</code> is not a
357 * valid principal name
358 */
359 public synchronized int getPermissions(String principal) {
360 int permissions = 0;
361
362 if (!(ALL_PRINCIPALS.equals(principal))) {
363 checkPrincipal(principal);
364 Object po = principalPermissions.get(principal);
365 if (po != null)
366 permissions = ((Integer) po).intValue();
367 }
368
369 return permissions | globalPermissions;
370 }
371
372 /**
373 * Check whether the given permissions are granted to a certain principal.
374 * The requested permissions are specified as a bitfield, for example
375 * <code>(Acl.ADD | Acl.DELETE | Acl.GET)</code>.
376 *
377 * @param principal The entity to check, or &quot;*&quot; to check whether
378 * the given permissions are granted to all principals globally
379 * @param permissions The permissions to check
380 * @return <code>true</code> if the principal holds all the given permissions
381 * @throws IllegalArgumentException if <code>principal</code> is not a
382 * valid principal name or if <code>permissions</code> is not a
383 * valid combination of the permission constants defined in this
384 * class
385 */
386 public synchronized boolean isPermitted(String principal, int permissions) {
387 checkPermissions(permissions);
388
389 int hasPermissions = getPermissions(principal);
390 return (permissions & hasPermissions) == permissions;
391 }
392
393 /**
394 * Create a new <code>Acl</code> instance from this <code>Acl</code> where
395 * all permissions for the given principal are overwritten with the given
396 * permissions.
397 * <p>
398 * Note, that when changing the permissions of a specific principal, it is
399 * not allowed to specify a set of permissions stricter than the global set
400 * of permissions (that apply to all principals).
401 *
402 * @param principal The entity to which permissions should be granted, or
403 * &quot;*&quot; to globally grant permissions to all principals.
404 * @param permissions The set of permissions to be given. The parameter is
405 * a bitmask of the permission constants defined in this class.
406 * @return a new <code>Acl</code> instance
407 * @throws IllegalArgumentException if <code>principal</code> is not a
408 * valid principal name, if <code>permissions</code> is not a
409 * valid combination of the permission constants defined in this
410 * class, or if a globally granted permission would have been
411 * revoked from a specific principal
412 */
413 public synchronized Acl setPermission(String principal, int permissions) {
414 checkPermissions(permissions);
415
416 Acl newPermission = new Acl(this, principal, permissions);
417 return newPermission;
418 }
419
420 /**
421 * Get the list of principals who have any kind of permissions on this node.
422 * The list only includes those principals that have been explicitly
423 * assigned permissions (so &quot;*&quot; is never returned), globally set
424 * permissions naturally apply to all other principals as well.
425 *
426 * @return The array of principals having permissions on this node.
427 */
428 public String[] getPrincipals() {
429 return (String[]) (principalPermissions.keySet().toArray(new String[0]));
430 }
431
432 /**
433 * Give the canonic string representation of this ACL. The operations are in
434 * the following order: {Add, Delete, Exec, Get, Replace}, principal names
435 * are sorted alphabetically.
436 *
437 * @return The string representation as defined in OMA DM.
438 */
439 public synchronized String toString() {
440 String acl = null;
441 for (int i = 0; i < PERMISSION_CODES.length; i++)
442 acl = writeEntry(PERMISSION_CODES[i], acl);
443
444 return (acl != null) ? acl : "";
445 }
446
447 // ----- Private utility methods -----//
448
449 private String writeEntry(int command, String acl) {
450 String aclEntry = null;
451
452 if ((command & globalPermissions) > 0)
453 aclEntry = ALL_PRINCIPALS;
454 else {
455 // TreeMap guarantees alphabetical ordering of keys during traversal
456 Iterator i = principalPermissions.entrySet().iterator();
457 while (i.hasNext()) {
458 Map.Entry entry = (Map.Entry) i.next();
459 if ((command & ((Integer) entry.getValue()).intValue()) > 0)
460 aclEntry = appendEntry(aclEntry, '+', (String) entry
461 .getKey());
462 }
463 }
464
465 if (aclEntry == null)
466 return acl;
467
468 return appendEntry(acl, '&', writeCommands(command) + '=' + aclEntry);
469 }
470
471 private static void deleteFromAll(TreeMap principalPermissions, int perm) {
472 Iterator i = principalPermissions.entrySet().iterator();
473 while (i.hasNext()) {
474 Map.Entry entry = (Map.Entry) i.next();
475 setPrincipalPermission(principalPermissions, (String) entry
476 .getKey(), ((Integer) entry.getValue()).intValue() & ~perm);
477 }
478 }
479
480 private static void setPrincipalPermission(TreeMap principalPermissions,
481 String principal, int perm) {
482 if (perm == 0)
483 principalPermissions.remove(principal);
484 else
485 principalPermissions.put(principal, new Integer(perm));
486 }
487
488 private static String writeCommands(int command) {
489 String commandStr = null;
490 for (int i = 0; i < PERMISSION_CODES.length; i++)
491 if ((command & PERMISSION_CODES[i]) != 0)
492 commandStr = appendEntry(commandStr, ',', PERMISSION_NAMES[i]);
493
494 return (commandStr != null) ? commandStr : "";
495 }
496
497 private static String appendEntry(String base, char separator, String entry) {
498 return (base != null) ? base + separator + entry : entry;
499 }
500
501 private static int parseCommand(String command) {
502 int i = Arrays.asList(PERMISSION_NAMES).indexOf(command);
503 if (i == -1)
504 throw new IllegalArgumentException(
505 "Invalid ACL string: unknown command '" + command + "'.");
506
507 return PERMISSION_CODES[i];
508 }
509
510 private static void checkPermissions(int perm) {
511 if ((perm & ~ALL_PERMISSION) != 0)
512 throw new IllegalArgumentException("Invalid ACL permission value: "
513 + perm);
514 }
515
516 private static void checkPrincipal(String principal) {
517 if (principal == null)
518 throw new IllegalArgumentException("Principal is null.");
519
520 checkServerId(principal, "Principal name contains illegal character");
521 }
522
523 private static void checkServerId(String serverId, String errorText) {
524 char[] chars = serverId.toCharArray();
525 for (int i = 0; i < chars.length; i++)
526 if ("*=+&".indexOf(chars[i]) != -1
527 || Character.isWhitespace(chars[i]))
528 throw new IllegalArgumentException(errorText + " '" + chars[i]
529 + "'.");
530 }
531
532 private static String[] split(String input, char sep, int limit) {
533 Vector v = new Vector();
534 boolean limited = (limit > 0);
535 int applied = 0;
536 int index = 0;
537 StringBuffer part = new StringBuffer();
538
539 while (index < input.length()) {
540 char ch = input.charAt(index);
541 if (ch != sep)
542 part.append(ch);
543 else {
544 ++applied;
545 v.add(part.toString());
546 part = new StringBuffer();
547 }
548 ++index;
549 if (limited && applied == limit - 1)
550 break;
551 }
552 while (index < input.length()) {
553 char ch = input.charAt(index);
554 part.append(ch);
555 ++index;
556 }
557 v.add(part.toString());
558
559 int last = v.size();
560 if (0 == limit) {
561 for (int j = v.size() - 1; j >= 0; --j) {
562 String s = (String) v.elementAt(j);
563 if ("".equals(s))
564 --last;
565 else
566 break;
567 }
568 }
569
570 String[] ret = new String[last];
571 for (int i = 0; i < last; ++i)
572 ret[i] = (String) v.elementAt(i);
573
574 return ret;
575 }
576}