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