/* * $Header: /cvshome/build/org.osgi.service.useradmin/src/org/osgi/service/useradmin/UserAdminPermission.java,v 1.11 2006/06/16 16:31:41 hargrave Exp $ * * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.osgi.service.useradmin; import java.io.IOException; import java.util.Hashtable; import java.util.Enumeration; import java.security.Permission; import java.security.BasicPermission; import java.security.PermissionCollection; /** * Permission to configure and access the {@link Role}objects managed by a User * Admin service. * *

* This class represents access to the Role objects managed by a User * Admin service and their properties and credentials (in the case of * {@link User}objects). *

* The permission name is the name (or name prefix) of a property or credential. * The naming convention follows the hierarchical property naming convention. * Also, an asterisk may appear at the end of the name, following a * ".", or by itself, to signify a wildcard match. For example: * "org.osgi.security.protocol.*" or "*" is valid, but * "*protocol" or "a*b" are not valid. * *

* The UserAdminPermission with the reserved name "admin" * represents the permission required for creating and removing Role * objects in the User Admin service, as well as adding and removing members in * a Group object. This UserAdminPermission does not have * any actions associated with it. * *

* The actions to be granted are passed to the constructor in a string * containing a list of one or more comma-separated keywords. The possible * keywords are: changeProperty,changeCredential, and * getCredential. Their meaning is defined as follows: * *

 * 
 *  action
 *  changeProperty    Permission to change (i.e., add and remove)
 *                    Role object properties whose names start with
 *                    the name argument specified in the constructor.
 *  changeCredential  Permission to change (i.e., add and remove)
 *                    User object credentials whose names start
 *                    with the name argument specified in the constructor.
 *  getCredential     Permission to retrieve and check for the
 *                    existence of User object credentials whose names
 *                    start with the name argument specified in the
 *                    constructor.
 *  
 * 
* * The action string is converted to lowercase before processing. * *

* Following is a PermissionInfo style policy entry which grants a user * administration bundle a number of UserAdminPermission object: * *

 * 
 *  (org.osgi.service.useradmin.UserAdminPermission "admin")
 *  (org.osgi.service.useradmin.UserAdminPermission "com.foo.*" "changeProperty,getCredential,changeCredential")
 *  (org.osgi.service.useradmin.UserAdminPermission "user.*", "changeProperty,changeCredential")
 *  
 * 
* * The first permission statement grants the bundle the permission to perform * any User Admin service operations of type "admin", that is, create and remove * roles and configure Group objects. * *

* The second permission statement grants the bundle the permission to change * any properties as well as get and change any credentials whose names start * with com.foo.. * *

* The third permission statement grants the bundle the permission to change any * properties and credentials whose names start with user.. This * means that the bundle is allowed to change, but not retrieve any credentials * with the given prefix. * *

* The following policy entry empowers the Http Service bundle to perform user * authentication: * *

 * 
 *  grant codeBase "${jars}http.jar" {
 *    permission org.osgi.service.useradmin.UserAdminPermission
 *      "user.password", "getCredential";
 *  };
 *  
 * 
* *

* The permission statement grants the Http Service bundle the permission to * validate any password credentials (for authentication purposes), but the * bundle is not allowed to change any properties or credentials. * * @version $Revision: 1.11 $ */ public final class UserAdminPermission extends BasicPermission { static final long serialVersionUID = -1179971692401603789L; /** * The permission name "admin". */ public static final String ADMIN = "admin"; /** * The action string "changeProperty". */ public static final String CHANGE_PROPERTY = "changeProperty"; private static final int ACTION_CHANGE_PROPERTY = 0x1; /** * The action string "changeCredential". */ public static final String CHANGE_CREDENTIAL = "changeCredential"; private static final int ACTION_CHANGE_CREDENTIAL = 0x2; /** * The action string "getCredential". */ public static final String GET_CREDENTIAL = "getCredential"; private static final int ACTION_GET_CREDENTIAL = 0x4; /** * All actions */ private static final int ACTION_ALL = ACTION_CHANGE_PROPERTY | ACTION_CHANGE_CREDENTIAL | ACTION_GET_CREDENTIAL; /** * No actions. */ static final int ACTION_NONE = 0x0; /** * The actions in canonical form. * * @serial */ private String actions = null; /** * The actions mask. */ private transient int action_mask = ACTION_NONE; /* * Description of this UserAdminPermission (returned by * toString ) */ private transient String description; /** * Creates a new UserAdminPermission with the specified name and * actions. name is either the reserved string "admin" * or the name of a credential or property, and actions contains * a comma-separated list of the actions granted on the specified name. * Valid actions are changeProperty,changeCredential, * and getCredential. * * @param name the name of this UserAdminPermission * @param actions the action string. * * @throws IllegalArgumentException If name equals * "admin" and actions are specified. */ public UserAdminPermission(String name, String actions) { this(name, getMask(actions)); } /** * Package private constructor used by * UserAdminPermissionCollection. * * @param name class name * @param mask action mask */ UserAdminPermission(String name, int mask) { super(name); init(mask); } /** * Called by constructors and when deserialized. * * @param mask action mask */ private void init(int mask) { if (getName().equals(ADMIN)) { if (mask != ACTION_NONE) { throw new IllegalArgumentException("Actions specified for " + "no-action " + "UserAdminPermission"); } } else { if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) { throw new IllegalArgumentException("Invalid action string"); } } action_mask = mask; } /** * Parses the action string into the action mask. * * @param actions Action string. * @return action mask. */ private static int getMask(String actions) { boolean seencomma = false; int mask = ACTION_NONE; if (actions == null) { return (mask); } char[] a = actions.toCharArray(); int i = a.length - 1; if (i < 0) return (mask); while (i != -1) { char c; // skip whitespace while ((i != -1) && ((c = a[i]) == ' ' || c == '\r' || c == '\n' || c == '\f' || c == '\t')) i--; // check for the known strings int matchlen; if (i >= 12 && match_get(a, i - 10) && match_credential(a, i)) { matchlen = 13; mask |= ACTION_GET_CREDENTIAL; } else if (i >= 13 && match_change(a, i - 8) && match_property(a, i)) { matchlen = 14; mask |= ACTION_CHANGE_PROPERTY; } else if (i >= 15 && match_change(a, i - 10) && match_credential(a, i)) { matchlen = 16; mask |= ACTION_CHANGE_CREDENTIAL; } else { // parse error throw new IllegalArgumentException( "invalid permission: " + actions); } // make sure we didn't just match the tail of a word // like "ackbarfimport". Also, skip to the comma. seencomma = false; while (i >= matchlen && !seencomma) { switch (a[i - matchlen]) { case ',' : seencomma = true; /* FALLTHROUGH */ case ' ' : case '\r' : case '\n' : case '\f' : case '\t' : break; default : throw new IllegalArgumentException( "invalid permission: " + actions); } i--; } // point i at the location of the comma minus one (or -1). i -= matchlen; } if (seencomma) { throw new IllegalArgumentException("invalid permission: " + actions); } return (mask); } private static boolean match_change(char[] a, int i) { return ((a[i - 5] == 'c' || a[i - 5] == 'C') && (a[i - 4] == 'h' || a[i - 4] == 'H') && (a[i - 3] == 'a' || a[i - 3] == 'A') && (a[i - 2] == 'n' || a[i - 2] == 'N') && (a[i - 1] == 'g' || a[i - 1] == 'G') && (a[i - 0] == 'e' || a[i - 0] == 'E')); } private static boolean match_get(char[] a, int i) { return ((a[i - 2] == 'g' || a[i - 2] == 'G') && (a[i - 1] == 'e' || a[i - 1] == 'E') && (a[i - 0] == 't' || a[i - 0] == 'T')); } private static boolean match_property(char[] a, int i) { return ((a[i - 7] == 'p' || a[i - 7] == 'P') && (a[i - 6] == 'r' || a[i - 6] == 'R') && (a[i - 5] == 'o' || a[i - 5] == 'O') && (a[i - 4] == 'p' || a[i - 4] == 'P') && (a[i - 3] == 'e' || a[i - 3] == 'E') && (a[i - 2] == 'r' || a[i - 2] == 'R') && (a[i - 1] == 't' || a[i - 1] == 'T') && (a[i - 0] == 'y' || a[i - 0] == 'Y')); } private static boolean match_credential(char[] a, int i) { return ((a[i - 9] == 'c' || a[i - 9] == 'C') && (a[i - 8] == 'r' || a[i - 8] == 'R') && (a[i - 7] == 'e' || a[i - 7] == 'E') && (a[i - 6] == 'd' || a[i - 6] == 'D') && (a[i - 5] == 'e' || a[i - 5] == 'E') && (a[i - 4] == 'n' || a[i - 4] == 'N') && (a[i - 3] == 't' || a[i - 3] == 'T') && (a[i - 2] == 'i' || a[i - 2] == 'I') && (a[i - 1] == 'a' || a[i - 1] == 'A') && (a[i - 0] == 'l' || a[i - 0] == 'L')); } /** * Checks if this UserAdminPermission object "implies" * the specified permission. *

* More specifically, this method returns true if: *

*

* * @param p the permission to check against. * * @return true if the specified permission is implied by this * object; false otherwise. */ public boolean implies(Permission p) { if (p instanceof UserAdminPermission) { UserAdminPermission target = (UserAdminPermission) p; return (// Check that the we have the requested action ((target.action_mask & action_mask) == target.action_mask) && // If the target action mask is ACTION_NONE, it must be an // admin permission, and then we must be that too (target.action_mask != ACTION_NONE || action_mask == ACTION_NONE) && // Check that name name matches super.implies(p)); } else { return (false); } } /** * Returns the canonical string representation of the actions, separated by * comma. * * @return the canonical string representation of the actions. */ public String getActions() { if (actions == null) { StringBuffer sb = new StringBuffer(); boolean comma = false; if ((action_mask & ACTION_CHANGE_CREDENTIAL) == ACTION_CHANGE_CREDENTIAL) { sb.append(CHANGE_CREDENTIAL); comma = true; } if ((action_mask & ACTION_CHANGE_PROPERTY) == ACTION_CHANGE_PROPERTY) { if (comma) sb.append(','); sb.append(CHANGE_PROPERTY); comma = true; } if ((action_mask & ACTION_GET_CREDENTIAL) == ACTION_GET_CREDENTIAL) { if (comma) sb.append(','); sb.append(GET_CREDENTIAL); } actions = sb.toString(); } return (actions); } /** * Returns a new PermissionCollection object for storing * UserAdminPermission objects. * * @return a new PermissionCollection object suitable for storing * UserAdminPermission objects. */ public PermissionCollection newPermissionCollection() { return (new UserAdminPermissionCollection()); } /** * Checks two UserAdminPermission objects for equality. Checks * that obj is a UserAdminPermission, and has the * same name and actions as this object. * * @param obj the object to be compared for equality with this object. * * @return true if obj is a * UserAdminPermission object, and has the same name and * actions as this UserAdminPermission object. */ public boolean equals(Object obj) { if (obj == this) { return (true); } if (obj instanceof UserAdminPermission) { UserAdminPermission uap = (UserAdminPermission) obj; return ((action_mask == uap.action_mask) && getName().equals( uap.getName())); } else { return (false); } } /** * Returns the hash code of this UserAdminPermission object. */ public int hashCode() { return (getName().hashCode() ^ getActions().hashCode()); } /** * Returns the current action mask. Used by the * UserAdminPermissionCollection class. * * @return the actions mask. */ int getMask() { return (action_mask); } /** * writeObject is called to save the state of this object to a stream. The * actions are serialized, and the superclass takes care of the name. */ private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException { // Write out the actions. The superclass takes care of the name // call getActions to make sure actions field is initialized if (actions == null) getActions(); s.defaultWriteObject(); } /* * Restores this object from a stream (i.e., deserializes it). */ private synchronized void readObject(java.io.ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); init(getMask(actions)); } /** * Returns a string describing this UserAdminPermission object. * This string must be in PermissionInfo encoded format. * * @return The PermissionInfo encoded string for this * UserAdminPermission object. * @see "org.osgi.service.permissionadmin.PermissionInfo.getEncoded" */ public String toString() { if (description == null) { StringBuffer sb = new StringBuffer(); sb.append('('); sb.append(getClass().getName()); sb.append(" \""); sb.append(getName()); String actions = getActions(); if (actions.length() > 0) { sb.append("\" \""); sb.append(actions); } sb.append("\")"); description = sb.toString(); } return (description); } } /** * A UserAdminPermissionCollection stores a set of * UserAdminPermission permissions. */ final class UserAdminPermissionCollection extends PermissionCollection { static final long serialVersionUID = -7222111885230120581L; /** * Table of permissions. * * @serial */ private Hashtable permissions; /** * Boolean saying if "*" is in the collection. * * @serial */ private boolean all_allowed; /** * Creates an empty UserAdminPermissionCollection object. */ public UserAdminPermissionCollection() { permissions = new Hashtable(); all_allowed = false; } /** * Adds the given permission to this UserAdminPermissionCollection. * The key for the hash is the name. * * @param permission the Permission object to add. * * @throws IllegalArgumentException If the given permission is not a * UserAdminPermission * @throws SecurityException If this UserAdminPermissionCollection * object has been marked readonly */ public void add(Permission permission) { if (!(permission instanceof UserAdminPermission)) throw new IllegalArgumentException("Invalid permission: " + permission); if (isReadOnly()) { throw new SecurityException("Attempt to add a Permission to a " + "readonly PermissionCollection"); } UserAdminPermission uap = (UserAdminPermission) permission; String name = uap.getName(); UserAdminPermission existing = (UserAdminPermission) permissions .get(name); if (existing != null) { int oldMask = existing.getMask(); int newMask = uap.getMask(); if (oldMask != newMask) { permissions.put(name, new UserAdminPermission(name, oldMask | newMask)); } } else { permissions.put(name, permission); } if (!all_allowed) { if (name.equals("*")) all_allowed = true; } } /** * Checks to see if this PermissionCollection implies the given * permission. * * @param permission the Permission object to check against * * @return true if the given permission is implied by this * PermissionCollection, false otherwise. */ public boolean implies(Permission permission) { if (!(permission instanceof UserAdminPermission)) { return (false); } UserAdminPermission uap = (UserAdminPermission) permission; UserAdminPermission x; int desired = uap.getMask(); int effective = 0; // Short circuit if the "*" Permission was added. // desired can only be ACTION_NONE when name is "admin". if (all_allowed && desired != UserAdminPermission.ACTION_NONE) { x = (UserAdminPermission) permissions.get("*"); if (x != null) { effective |= x.getMask(); if ((effective & desired) == desired) { return (true); } } } // strategy: // Check for full match first. Then work our way up the // name looking for matches on a.b.* String name = uap.getName(); x = (UserAdminPermission) permissions.get(name); if (x != null) { // we have a direct hit! effective |= x.getMask(); if ((effective & desired) == desired) { return (true); } } // work our way up the tree... int last; int offset = name.length() - 1; while ((last = name.lastIndexOf(".", offset)) != -1) { name = name.substring(0, last + 1) + "*"; x = (UserAdminPermission) permissions.get(name); if (x != null) { effective |= x.getMask(); if ((effective & desired) == desired) { return (true); } } offset = last - 1; } // we don't have to check for "*" as it was already checked // at the top (all_allowed), so we just return false return (false); } /** * Returns an enumeration of all the UserAdminPermission objects * in the container. * * @return an enumeration of all the UserAdminPermission objects. */ public Enumeration elements() { return (permissions.elements()); } }