001 /*
002 * Copyright (c) OSGi Alliance (2001, 2009). All Rights Reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.osgi.service.useradmin;
017
018 import java.io.IOException;
019 import java.security.BasicPermission;
020 import java.security.Permission;
021 import java.security.PermissionCollection;
022 import java.util.Enumeration;
023 import java.util.Hashtable;
024
025 /**
026 * Permission to configure and access the {@link Role} objects managed by a User
027 * Admin service.
028 *
029 * <p>
030 * This class represents access to the <code>Role</code> objects managed by a
031 * User Admin service and their properties and credentials (in the case of
032 * {@link User} objects).
033 * <p>
034 * The permission name is the name (or name prefix) of a property or credential.
035 * The naming convention follows the hierarchical property naming convention.
036 * Also, an asterisk may appear at the end of the name, following a
037 * ".", or by itself, to signify a wildcard match. For example:
038 * "org.osgi.security.protocol.*" or "*" is valid, but
039 * "*protocol" or "a*b" are not valid.
040 *
041 * <p>
042 * The <code>UserAdminPermission</code> with the reserved name "admin"
043 * represents the permission required for creating and removing
044 * <code>Role</code> objects in the User Admin service, as well as adding and
045 * removing members in a <code>Group</code> object. This
046 * <code>UserAdminPermission</code> does not have any actions associated with
047 * it.
048 *
049 * <p>
050 * The actions to be granted are passed to the constructor in a string
051 * containing a list of one or more comma-separated keywords. The possible
052 * keywords are: <code>changeProperty</code>,<code>changeCredential</code>, and
053 * <code>getCredential</code>. Their meaning is defined as follows:
054 *
055 * <pre>
056 *
057 * action
058 * changeProperty Permission to change (i.e., add and remove)
059 * Role object properties whose names start with
060 * the name argument specified in the constructor.
061 * changeCredential Permission to change (i.e., add and remove)
062 * User object credentials whose names start
063 * with the name argument specified in the constructor.
064 * getCredential Permission to retrieve and check for the
065 * existence of User object credentials whose names
066 * start with the name argument specified in the
067 * constructor.
068 *
069 * </pre>
070 *
071 * The action string is converted to lowercase before processing.
072 *
073 * <p>
074 * Following is a PermissionInfo style policy entry which grants a user
075 * administration bundle a number of <code>UserAdminPermission</code> object:
076 *
077 * <pre>
078 *
079 * (org.osgi.service.useradmin.UserAdminPermission "admin")
080 * (org.osgi.service.useradmin.UserAdminPermission "com.foo.*" "changeProperty,getCredential,changeCredential")
081 * (org.osgi.service.useradmin.UserAdminPermission "user.*", "changeProperty,changeCredential")
082 *
083 * </pre>
084 *
085 * The first permission statement grants the bundle the permission to perform
086 * any User Admin service operations of type "admin", that is, create and remove
087 * roles and configure <code>Group</code> objects.
088 *
089 * <p>
090 * The second permission statement grants the bundle the permission to change
091 * any properties as well as get and change any credentials whose names start
092 * with <code>com.foo.</code>.
093 *
094 * <p>
095 * The third permission statement grants the bundle the permission to change any
096 * properties and credentials whose names start with <code>user.</code>. This
097 * means that the bundle is allowed to change, but not retrieve any credentials
098 * with the given prefix.
099 *
100 * <p>
101 * The following policy entry empowers the Http Service bundle to perform user
102 * authentication:
103 *
104 * <pre>
105 *
106 * grant codeBase "${jars}http.jar" {
107 * permission org.osgi.service.useradmin.UserAdminPermission
108 * "user.password", "getCredential";
109 * };
110 *
111 * </pre>
112 *
113 * <p>
114 * The permission statement grants the Http Service bundle the permission to
115 * validate any password credentials (for authentication purposes), but the
116 * bundle is not allowed to change any properties or credentials.
117 *
118 * @ThreadSafe
119 * @version $Revision: 6381 $
120 */
121 public final class UserAdminPermission extends BasicPermission {
122 static final long serialVersionUID = -1179971692401603789L;
123 /**
124 * The permission name "admin".
125 */
126 public static final String ADMIN = "admin";
127 /**
128 * The action string "changeProperty".
129 */
130 public static final String CHANGE_PROPERTY = "changeProperty";
131 private static final int ACTION_CHANGE_PROPERTY = 0x1;
132 /**
133 * The action string "changeCredential".
134 */
135 public static final String CHANGE_CREDENTIAL = "changeCredential";
136 private static final int ACTION_CHANGE_CREDENTIAL = 0x2;
137 /**
138 * The action string "getCredential".
139 */
140 public static final String GET_CREDENTIAL = "getCredential";
141 private static final int ACTION_GET_CREDENTIAL = 0x4;
142 /**
143 * All actions
144 */
145 private static final int ACTION_ALL = ACTION_CHANGE_PROPERTY
146 | ACTION_CHANGE_CREDENTIAL
147 | ACTION_GET_CREDENTIAL;
148 /**
149 * No actions.
150 */
151 static final int ACTION_NONE = 0;
152 /**
153 * The actions in canonical form.
154 *
155 * @serial
156 */
157 private volatile String actions = null;
158 /**
159 * The actions mask.
160 */
161 private transient int action_mask;
162
163 /**
164 * Creates a new <code>UserAdminPermission</code> with the specified name
165 * and actions. <code>name</code> is either the reserved string
166 * "admin" or the name of a credential or property, and
167 * <code>actions</code> contains a comma-separated list of the actions
168 * granted on the specified name. Valid actions are
169 * <code>changeProperty</code>,<code>changeCredential</code>, and
170 * getCredential.
171 *
172 * @param name the name of this <code>UserAdminPermission</code>
173 * @param actions the action string.
174 *
175 * @throws IllegalArgumentException If <code>name</code> equals
176 * "admin" and <code>actions</code> are specified.
177 */
178 public UserAdminPermission(String name, String actions) {
179 this(name, parseActions(actions));
180 }
181
182 /**
183 * Package private constructor used by
184 * <code>UserAdminPermissionCollection</code>.
185 *
186 * @param name class name
187 * @param mask action mask
188 */
189 UserAdminPermission(String name, int mask) {
190 super(name);
191 setTransients(mask);
192 }
193
194 /**
195 * Called by constructors and when deserialized.
196 *
197 * @param mask action mask
198 */
199 private synchronized void setTransients(int mask) {
200 if (getName().equals(ADMIN)) {
201 if (mask != ACTION_NONE) {
202 throw new IllegalArgumentException("Actions specified for "
203 + "no-action " + "UserAdminPermission");
204 }
205 }
206 else {
207 if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
208 throw new IllegalArgumentException("Invalid action string");
209 }
210 }
211 action_mask = mask;
212 }
213
214 /**
215 * Returns the current action mask.
216 * <p>
217 * Used by the UserAdminPermissionCollection class.
218 *
219 * @return Current action mask.
220 */
221 synchronized int getActionsMask() {
222 return action_mask;
223 }
224
225 /**
226 * Parse action string into action mask.
227 *
228 * @param actions Action string.
229 * @return action mask.
230 */
231 private static int parseActions(String actions) {
232 boolean seencomma = false;
233 int mask = ACTION_NONE;
234 if (actions == null) {
235 return mask;
236 }
237 char[] a = actions.toCharArray();
238 int i = a.length - 1;
239 if (i < 0)
240 return mask;
241 while (i != -1) {
242 char c;
243 // skip whitespace
244 while ((i != -1)
245 && ((c = a[i]) == ' ' || c == '\r' || c == '\n'
246 || c == '\f' || c == '\t'))
247 i--;
248 // check for the known strings
249 int matchlen;
250 if (i >= 12 && match_get(a, i - 10) && match_credential(a, i)) {
251 matchlen = 13;
252 mask |= ACTION_GET_CREDENTIAL;
253 }
254 else
255 if (i >= 13 && match_change(a, i - 8) && match_property(a, i)) {
256 matchlen = 14;
257 mask |= ACTION_CHANGE_PROPERTY;
258 }
259 else
260 if (i >= 15 && match_change(a, i - 10)
261 && match_credential(a, i)) {
262 matchlen = 16;
263 mask |= ACTION_CHANGE_CREDENTIAL;
264 }
265 else {
266 // parse error
267 throw new IllegalArgumentException(
268 "invalid permission: " + actions);
269 }
270 // make sure we didn't just match the tail of a word
271 // like "ackbarfimport". Also, skip to the comma.
272 seencomma = false;
273 while (i >= matchlen && !seencomma) {
274 switch (a[i - matchlen]) {
275 case ',' :
276 seencomma = true;
277 /* FALLTHROUGH */
278 case ' ' :
279 case '\r' :
280 case '\n' :
281 case '\f' :
282 case '\t' :
283 break;
284 default :
285 throw new IllegalArgumentException(
286 "invalid permission: " + actions);
287 }
288 i--;
289 }
290 // point i at the location of the comma minus one (or -1).
291 i -= matchlen;
292 }
293 if (seencomma) {
294 throw new IllegalArgumentException("invalid permission: " + actions);
295 }
296 return mask;
297 }
298
299 private static boolean match_change(char[] a, int i) {
300 return ((a[i - 5] == 'c' || a[i - 5] == 'C')
301 && (a[i - 4] == 'h' || a[i - 4] == 'H')
302 && (a[i - 3] == 'a' || a[i - 3] == 'A')
303 && (a[i - 2] == 'n' || a[i - 2] == 'N')
304 && (a[i - 1] == 'g' || a[i - 1] == 'G') && (a[i - 0] == 'e' || a[i - 0] == 'E'));
305 }
306
307 private static boolean match_get(char[] a, int i) {
308 return ((a[i - 2] == 'g' || a[i - 2] == 'G')
309 && (a[i - 1] == 'e' || a[i - 1] == 'E') && (a[i - 0] == 't' || a[i - 0] == 'T'));
310 }
311
312 private static boolean match_property(char[] a, int i) {
313 return ((a[i - 7] == 'p' || a[i - 7] == 'P')
314 && (a[i - 6] == 'r' || a[i - 6] == 'R')
315 && (a[i - 5] == 'o' || a[i - 5] == 'O')
316 && (a[i - 4] == 'p' || a[i - 4] == 'P')
317 && (a[i - 3] == 'e' || a[i - 3] == 'E')
318 && (a[i - 2] == 'r' || a[i - 2] == 'R')
319 && (a[i - 1] == 't' || a[i - 1] == 'T') && (a[i - 0] == 'y' || a[i - 0] == 'Y'));
320 }
321
322 private static boolean match_credential(char[] a, int i) {
323 return ((a[i - 9] == 'c' || a[i - 9] == 'C')
324 && (a[i - 8] == 'r' || a[i - 8] == 'R')
325 && (a[i - 7] == 'e' || a[i - 7] == 'E')
326 && (a[i - 6] == 'd' || a[i - 6] == 'D')
327 && (a[i - 5] == 'e' || a[i - 5] == 'E')
328 && (a[i - 4] == 'n' || a[i - 4] == 'N')
329 && (a[i - 3] == 't' || a[i - 3] == 'T')
330 && (a[i - 2] == 'i' || a[i - 2] == 'I')
331 && (a[i - 1] == 'a' || a[i - 1] == 'A') && (a[i - 0] == 'l' || a[i - 0] == 'L'));
332 }
333
334 /**
335 * Checks if this <code>UserAdminPermission</code> object
336 * "implies" the specified permission.
337 * <P>
338 * More specifically, this method returns <code>true</code> if:
339 * <p>
340 * <ul>
341 * <li><i>p </i> is an instanceof <code>UserAdminPermission</code>,
342 * <li><i>p </i>'s actions are a proper subset of this object's actions, and
343 * <li><i>p </i>'s name is implied by this object's name. For example,
344 * "java.*" implies "java.home".
345 * </ul>
346 *
347 * @param p the permission to check against.
348 *
349 * @return <code>true</code> if the specified permission is implied by this
350 * object; <code>false</code> otherwise.
351 */
352 public boolean implies(Permission p) {
353 if (p instanceof UserAdminPermission) {
354 UserAdminPermission requested = (UserAdminPermission) p;
355 int mask = getActionsMask();
356 int targetMask = requested.getActionsMask();
357 return // Check that the we have the requested action
358 ((targetMask & mask) == targetMask) &&
359 // If the target action mask is ACTION_NONE, it must be an
360 // admin permission, and then we must be that too
361 (targetMask != ACTION_NONE || mask == ACTION_NONE) &&
362 // Check that name name matches
363 super.implies(p);
364 }
365 return false;
366 }
367
368 /**
369 * Returns the canonical string representation of the actions, separated by
370 * comma.
371 *
372 * @return the canonical string representation of the actions.
373 */
374 public String getActions() {
375 String result = actions;
376 if (result == null) {
377 StringBuffer sb = new StringBuffer();
378 boolean comma = false;
379 int mask = getActionsMask();
380 if ((mask & ACTION_CHANGE_CREDENTIAL) == ACTION_CHANGE_CREDENTIAL) {
381 sb.append(CHANGE_CREDENTIAL);
382 comma = true;
383 }
384 if ((mask & ACTION_CHANGE_PROPERTY) == ACTION_CHANGE_PROPERTY) {
385 if (comma)
386 sb.append(',');
387 sb.append(CHANGE_PROPERTY);
388 comma = true;
389 }
390 if ((mask & ACTION_GET_CREDENTIAL) == ACTION_GET_CREDENTIAL) {
391 if (comma)
392 sb.append(',');
393 sb.append(GET_CREDENTIAL);
394 }
395 actions = result = sb.toString();
396 }
397 return result;
398 }
399
400 /**
401 * Returns a new <code>PermissionCollection</code> object for storing
402 * <code>UserAdminPermission</code> objects.
403 *
404 * @return a new <code>PermissionCollection</code> object suitable for
405 * storing <code>UserAdminPermission</code> objects.
406 */
407 public PermissionCollection newPermissionCollection() {
408 return new UserAdminPermissionCollection();
409 }
410
411 /**
412 * Checks two <code>UserAdminPermission</code> objects for equality. Checks
413 * that <code>obj</code> is a <code>UserAdminPermission</code>, and has the
414 * same name and actions as this object.
415 *
416 * @param obj the object to be compared for equality with this object.
417 *
418 * @return <code>true</code> if <code>obj</code> is a
419 * <code>UserAdminPermission</code> object, and has the same name
420 * and actions as this <code>UserAdminPermission</code> object.
421 */
422 public boolean equals(Object obj) {
423 if (obj == this) {
424 return true;
425 }
426 if (!(obj instanceof UserAdminPermission)) {
427 return false;
428 }
429
430 UserAdminPermission uap = (UserAdminPermission) obj;
431
432 return (getActionsMask() == uap.getActionsMask())
433 && getName().equals(uap.getName());
434 }
435
436 /**
437 * Returns the hash code value for this object.
438 *
439 * @return A hash code value for this object.
440 */
441 public int hashCode() {
442 int h = 31 * 17 + getName().hashCode();
443 h = 31 * h + getActions().hashCode();
444 return h;
445 }
446
447 /**
448 * writeObject is called to save the state of this object to a stream. The
449 * actions are serialized, and the superclass takes care of the name.
450 */
451 private synchronized void writeObject(java.io.ObjectOutputStream s)
452 throws IOException {
453 // Write out the actions. The superclass takes care of the name
454 // call getActions to make sure actions field is initialized
455 if (actions == null)
456 getActions();
457 s.defaultWriteObject();
458 }
459
460 /*
461 * Restores this object from a stream (i.e., deserializes it).
462 */
463 private synchronized void readObject(java.io.ObjectInputStream s)
464 throws IOException, ClassNotFoundException {
465 // Read in the action, then initialize the rest
466 s.defaultReadObject();
467 setTransients(parseActions(actions));
468 }
469
470 /**
471 * Returns a string describing this <code>UserAdminPermission</code> object.
472 * This string must be in <code>PermissionInfo</code> encoded format.
473 *
474 * @return The <code>PermissionInfo</code> encoded string for this
475 * <code>UserAdminPermission</code> object.
476 * @see "<code>org.osgi.service.permissionadmin.PermissionInfo.getEncoded</code>"
477 */
478 public String toString() {
479 StringBuffer sb = new StringBuffer();
480 sb.append('(');
481 sb.append(getClass().getName());
482 sb.append(" \"");
483 sb.append(getName());
484 String a = getActions();
485 if (a.length() > 0) {
486 sb.append("\" \"");
487 sb.append(a);
488 }
489 sb.append("\")");
490 return sb.toString();
491 }
492 }
493
494 /**
495 * A <code>UserAdminPermissionCollection</code> stores a set of
496 * <code>UserAdminPermission</code> permissions.
497 */
498
499 final class UserAdminPermissionCollection extends PermissionCollection {
500 static final long serialVersionUID = -7222111885230120581L;
501 /**
502 * Table of permissions.
503 *
504 * @serial
505 * @GuardedBy this
506 */
507 private final Hashtable permissions;
508 /**
509 * Boolean saying if "*" is in the collection.
510 *
511 * @serial
512 * @GuardedBy this
513 */
514 private boolean all_allowed;
515
516 /**
517 * Creates an empty <code>UserAdminPermissionCollection</code> object.
518 */
519 public UserAdminPermissionCollection() {
520 permissions = new Hashtable();
521 all_allowed = false;
522 }
523
524 /**
525 * Adds the given permission to this
526 * <code>UserAdminPermissionCollection</code>. The key for the hash is the
527 * name.
528 *
529 * @param permission the <code>Permission</code> object to add.
530 *
531 * @throws IllegalArgumentException If the given permission is not a
532 * <code>UserAdminPermission</code>
533 * @throws SecurityException If this
534 * <code>UserAdminPermissionCollection</code> object has been marked
535 * readonly
536 */
537 public void add(Permission permission) {
538 if (!(permission instanceof UserAdminPermission))
539 throw new IllegalArgumentException("Invalid permission: "
540 + permission);
541 if (isReadOnly()) {
542 throw new SecurityException("Attempt to add a Permission to a "
543 + "readonly PermissionCollection");
544 }
545 final UserAdminPermission uap = (UserAdminPermission) permission;
546 final String name = uap.getName();
547 synchronized (this) {
548 final UserAdminPermission existing = (UserAdminPermission) permissions
549 .get(name);
550 if (existing != null) {
551 int oldMask = existing.getActionsMask();
552 int newMask = uap.getActionsMask();
553 if (oldMask != newMask) {
554 permissions.put(name, new UserAdminPermission(name, oldMask
555 | newMask));
556 }
557 }
558 else {
559 permissions.put(name, uap);
560 }
561 if (!all_allowed) {
562 if (name.equals("*")) {
563 all_allowed = true;
564 }
565 }
566 }
567 }
568
569 /**
570 * Checks to see if this <code>PermissionCollection</code> implies the given
571 * permission.
572 *
573 * @param permission the <code>Permission</code> object to check against
574 *
575 * @return true if the given permission is implied by this
576 * <code>PermissionCollection</code>, false otherwise.
577 */
578 public boolean implies(Permission permission) {
579 if (!(permission instanceof UserAdminPermission)) {
580 return false;
581 }
582 final UserAdminPermission requested = (UserAdminPermission) permission;
583 String name = requested.getName();
584 final int desired = requested.getActionsMask();
585 UserAdminPermission x;
586 int effective = 0;
587 synchronized (this) {
588 // Short circuit if the "*" Permission was added.
589 // desired can only be ACTION_NONE when name is "admin".
590 if (all_allowed && (desired != UserAdminPermission.ACTION_NONE)) {
591 x = (UserAdminPermission) permissions.get("*");
592 if (x != null) {
593 effective |= x.getActionsMask();
594 if ((effective & desired) == desired) {
595 return true;
596 }
597 }
598 }
599 // strategy:
600 // Check for full match first. Then work our way up the
601 // name looking for matches on a.b.*
602
603 x = (UserAdminPermission) permissions.get(name);
604 }
605 if (x != null) {
606 // we have a direct hit!
607 effective |= x.getActionsMask();
608 if ((effective & desired) == desired) {
609 return true;
610 }
611 }
612 // work our way up the tree...
613 int last;
614 int offset = name.length() - 1;
615 while ((last = name.lastIndexOf(".", offset)) != -1) {
616 name = name.substring(0, last + 1) + "*";
617 synchronized (this) {
618 x = (UserAdminPermission) permissions.get(name);
619 }
620 if (x != null) {
621 effective |= x.getActionsMask();
622 if ((effective & desired) == desired) {
623 return true;
624 }
625 }
626 offset = last - 1;
627 }
628 // we don't have to check for "*" as it was already checked
629 // at the top (all_allowed), so we just return false
630 return false;
631 }
632
633 /**
634 * Returns an enumeration of all the <code>UserAdminPermission</code>
635 * objects in the container.
636 *
637 * @return an enumeration of all the <code>UserAdminPermission</code>
638 * objects.
639 */
640 public Enumeration elements() {
641 return permissions.elements();
642 }
643 }