| /* |
| * Copyright (c) OSGi Alliance (2004, 2008). 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 info.dmtree; |
| |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.Vector; |
| |
| /** |
| * <code>Acl</code> is an immutable class representing structured access to DMT |
| * ACLs. Under OMA DM the ACLs are defined as strings with an internal syntax. |
| * <p> |
| * The methods of this class taking a principal as parameter accept remote |
| * server IDs (as passed to {@link DmtAdmin#getSession(String, String, int) |
| * DmtAdmin.getSession}), as well as " <code>*</code> " indicating any |
| * principal. |
| * <p> |
| * The syntax for valid remote server IDs:<br> |
| * <<i>server-identifier</i>> ::= All printable characters except |
| * <code>'='</code>, <code>'&'</code>, <code>'*'</code>, <code>'+'</code> or white-space |
| * characters. |
| * |
| * @version $Revision: 5673 $ |
| */ |
| public final class Acl { |
| |
| // ----- Public constants -----// |
| |
| /** |
| * Principals holding this permission can issue GET command on the node |
| * having this ACL. |
| */ |
| public static final int GET = 1; |
| |
| /** |
| * Principals holding this permission can issue ADD commands on the node |
| * having this ACL. |
| */ |
| public static final int ADD = 2; |
| |
| /** |
| * Principals holding this permission can issue REPLACE commands on the node |
| * having this ACL. |
| */ |
| public static final int REPLACE = 4; |
| |
| /** |
| * Principals holding this permission can issue DELETE commands on the node |
| * having this ACL. |
| */ |
| public static final int DELETE = 8; |
| |
| /** |
| * Principals holding this permission can issue EXEC commands on the node |
| * having this ACL. |
| */ |
| public static final int EXEC = 16; |
| |
| /** |
| * Principals holding this permission can issue any command on the node |
| * having this ACL. This permission is the logical OR of {@link #ADD}, |
| * {@link #DELETE}, {@link #EXEC}, {@link #GET} and {@link #REPLACE} |
| * permissions. |
| */ |
| public static final int ALL_PERMISSION = ADD | DELETE | EXEC | GET |
| | REPLACE; |
| |
| // ----- Private constants -----// |
| |
| private static final int[] PERMISSION_CODES = new int[] { ADD, DELETE, |
| EXEC, GET, REPLACE }; |
| |
| private static final String[] PERMISSION_NAMES = new String[] { "Add", |
| "Delete", "Exec", "Get", "Replace" }; |
| |
| private static final String ALL_PRINCIPALS = "*"; |
| |
| // ----- Private fields -----// |
| |
| // the implementation takes advantage of this being a sorted map |
| private final TreeMap principalPermissions; |
| |
| private final int globalPermissions; |
| |
| // ----- Public constructors -----// |
| |
| /** |
| * Create an instance of the ACL from its canonic string representation. |
| * |
| * @param acl The string representation of the ACL as defined in OMA DM. If |
| * <code>null</code> or empty then it represents an empty list of |
| * principals with no permissions. |
| * @throws IllegalArgumentException if acl is not a valid OMA DM ACL string |
| */ |
| public Acl(String acl) { |
| if (acl == null || acl.equals("")) { // empty permission set |
| principalPermissions = new TreeMap(); |
| globalPermissions = 0; |
| return; |
| } |
| |
| TreeMap tempPrincipalPermissions = new TreeMap(); |
| int tempGlobalPermissions = 0; |
| |
| String[] aclEntries = split(acl, '&', -1); |
| for (int i = 0; i < aclEntries.length; i++) { |
| if (aclEntries[i].length() == 0) |
| throw new IllegalArgumentException( |
| "Invalid ACL string: empty ACL entry."); |
| |
| String[] entryParts = split(aclEntries[i], '=', 2); |
| if (entryParts.length == 1) |
| throw new IllegalArgumentException( |
| "Invalid ACL string: no '=' in ACL entry."); |
| if (entryParts[1].length() == 0) |
| throw new IllegalArgumentException( |
| "Invalid ACL string: no server identifiers in ACL entry."); |
| |
| int command = parseCommand(entryParts[0]); |
| String[] serverIds = split(entryParts[1], '+', -1); |
| for (int j = 0; j < serverIds.length; j++) { |
| if (serverIds[j].length() == 0) |
| throw new IllegalArgumentException( |
| "Invalid ACL string: empty server identifier."); |
| |
| if (serverIds[j].equals(ALL_PRINCIPALS)) |
| tempGlobalPermissions |= command; |
| else { |
| checkServerId(serverIds[j], "Invalid ACL string: " |
| + "server ID contains illegal character"); |
| Integer n = (Integer) tempPrincipalPermissions |
| .get(serverIds[j]); |
| int oldPermission = (n != null) ? n.intValue() : 0; |
| tempPrincipalPermissions.put(serverIds[j], new Integer( |
| oldPermission | command)); |
| } |
| } |
| } |
| |
| principalPermissions = tempPrincipalPermissions; |
| globalPermissions = tempGlobalPermissions; |
| } |
| |
| /** |
| * Creates an instance with a specified list of principals and the |
| * permissions they hold. The two arrays run in parallel, that is |
| * <code>principals[i]</code> will hold <code>permissions[i]</code> in |
| * the ACL. |
| * <p> |
| * A principal name may not appear multiple times in the 'principals' |
| * argument. If the "*" principal appears in the array, the |
| * corresponding permissions will be granted to all principals (regardless |
| * of whether they appear in the array or not). |
| * |
| * @param principals The array of principals |
| * @param permissions The array of permissions |
| * @throws IllegalArgumentException if the length of the two arrays are not |
| * the same, if any array element is invalid, or if a principal |
| * appears multiple times in the <code>principals</code> array |
| */ |
| public Acl(String[] principals, int[] permissions) { |
| if (principals.length != permissions.length) |
| throw new IllegalArgumentException( |
| "The lengths of the principal and permission arrays are not the same."); |
| |
| TreeMap tempPrincipalPermissions = new TreeMap(); |
| int tempGlobalPermissions = 0; |
| |
| for (int i = 0; i < principals.length; i++) { |
| // allow one * in 'principals' array, remove after loop |
| if (!ALL_PRINCIPALS.equals(principals[i])) |
| checkPrincipal(principals[i]); |
| checkPermissions(permissions[i]); |
| |
| Integer permInt = new Integer(permissions[i]); |
| Object old = tempPrincipalPermissions.put(principals[i], permInt); |
| if (old != null) |
| throw new IllegalArgumentException("Principal '" |
| + principals[i] |
| + "' appears multiple times in the principal array."); |
| } |
| |
| // set the global permissions if there was a * in the array |
| Object globalPermObj = tempPrincipalPermissions.remove(ALL_PRINCIPALS); |
| if (globalPermObj != null) |
| tempGlobalPermissions = ((Integer) globalPermObj).intValue(); |
| |
| principalPermissions = tempPrincipalPermissions; |
| globalPermissions = tempGlobalPermissions; |
| } |
| |
| // ----- Private constructors -----// |
| |
| /** |
| * Creates an instance identical to the <code>base</code> ACL except for |
| * the permissions of the given <code>principal</code>, which are |
| * overwritten with the given <code>permissions</code>. |
| * <p> |
| * Assumes that the permissions parameter has been checked. All |
| * modifications of an <code>Acl</code> (add, delete, set) are done |
| * through this method. |
| * |
| * @param base The ACL that provides all permissions except for permissions |
| * of the given principal. |
| * @param principal The entity to which permission should be granted. |
| * @param permissions The set of permissions to be given. The parameter can |
| * be a logical <code>or</code> of the permission constants defined |
| * in this class. |
| */ |
| private Acl(Acl base, String principal, int permissions) { |
| // make a shallow copy of the permission table, the keys (String) and |
| // values (Integer) are immutable anyway |
| TreeMap tempPrincipalPermissions = (TreeMap) base.principalPermissions |
| .clone(); |
| int tempGlobalPermissions = base.globalPermissions; |
| |
| int deletedGlobalPerm = tempGlobalPermissions & ~permissions; |
| if (ALL_PRINCIPALS.equals(principal)) { |
| deleteFromAll(tempPrincipalPermissions, deletedGlobalPerm); |
| tempGlobalPermissions = permissions; |
| } else { |
| checkPrincipal(principal); |
| |
| if (deletedGlobalPerm != 0) |
| throw new IllegalArgumentException( |
| "Cannot revoke globally set permissions (" |
| + writeCommands(deletedGlobalPerm) |
| + ") from a specific principal (" + principal |
| + ")."); |
| |
| setPrincipalPermission(tempPrincipalPermissions, principal, |
| permissions); |
| } |
| |
| principalPermissions = tempPrincipalPermissions; |
| globalPermissions = tempGlobalPermissions; |
| } |
| |
| // ----- Public methods -----// |
| |
| /** |
| * Checks whether the given object is equal to this <code>Acl</code> |
| * instance. Two <code>Acl</code> instances are equal if they allow the |
| * same set of permissions for the same set of principals. |
| * |
| * @param obj the object to compare with this <code>Acl</code> instance |
| * @return <code>true</code> if the parameter represents the same ACL as |
| * this instance |
| */ |
| public boolean equals(Object obj) { |
| if (obj == this) |
| return true; |
| |
| if (!(obj instanceof Acl)) |
| return false; |
| |
| Acl other = (Acl) obj; |
| |
| if (globalPermissions != other.globalPermissions |
| || principalPermissions.size() != other.principalPermissions |
| .size()) |
| return false; |
| |
| // principalPermissions sets cannot be easily compared, because they are |
| // not canonical: the global permissions may or may not be present for |
| // each principal, without changing the meaning of the Acl object. |
| |
| // Compare canonical string representations, inefficient but simple. |
| return toString().equals(other.toString()); |
| } |
| |
| /** |
| * Returns the hash code for this ACL instance. If two <code>Acl</code> |
| * instances are equal according to the {@link #equals} method, then calling |
| * this method on each of them must produce the same integer result. |
| * |
| * @return hash code for this ACL |
| */ |
| public int hashcode() { |
| // Using the hash code of the canonical string representation, because |
| // the principalPermissions set is not canonical (see above). |
| return toString().hashCode(); |
| } |
| |
| /** |
| * Create a new <code>Acl</code> instance from this <code>Acl</code> with |
| * the given permission added for the given principal. The already existing |
| * permissions of the principal are not affected. |
| * |
| * @param principal The entity to which permissions should be granted, or |
| * "*" to grant permissions to all principals. |
| * @param permissions The permissions to be given. The parameter can be a |
| * logical <code>or</code> of more permission constants defined in |
| * this class. |
| * @return a new <code>Acl</code> instance |
| * @throws IllegalArgumentException if <code>principal</code> is not a |
| * valid principal name or if <code>permissions</code> is not a |
| * valid combination of the permission constants defined in this |
| * class |
| */ |
| public synchronized Acl addPermission(String principal, int permissions) { |
| checkPermissions(permissions); |
| |
| int oldPermissions = getPermissions(principal); |
| return setPermission(principal, oldPermissions | permissions); |
| } |
| |
| /** |
| * Create a new <code>Acl</code> instance from this <code>Acl</code> with |
| * the given permission revoked from the given principal. Other permissions |
| * of the principal are not affected. |
| * <p> |
| * Note, that it is not valid to revoke a permission from a specific |
| * principal if that permission is granted globally to all principals. |
| * |
| * @param principal The entity from which permissions should be revoked, or |
| * "*" to revoke permissions from all principals. |
| * @param permissions The permissions to be revoked. The parameter can be a |
| * logical <code>or</code> of more permission constants defined in |
| * this class. |
| * @return a new <code>Acl</code> instance |
| * @throws IllegalArgumentException if <code>principal</code> is not a |
| * valid principal name, if <code>permissions</code> is not a |
| * valid combination of the permission constants defined in this |
| * class, or if a globally granted permission would have been |
| * revoked from a specific principal |
| */ |
| public synchronized Acl deletePermission(String principal, int permissions) { |
| checkPermissions(permissions); |
| |
| int oldPermissions = getPermissions(principal); |
| return setPermission(principal, oldPermissions & ~permissions); |
| } |
| |
| /** |
| * Get the permissions associated to a given principal. |
| * |
| * @param principal The entity whose permissions to query, or "*" |
| * to query the permissions that are granted globally, to all |
| * principals |
| * @return The permissions of the given principal. The returned |
| * <code>int</code> is a bitmask of the permission constants defined |
| * in this class |
| * @throws IllegalArgumentException if <code>principal</code> is not a |
| * valid principal name |
| */ |
| public synchronized int getPermissions(String principal) { |
| int permissions = 0; |
| |
| if (!(ALL_PRINCIPALS.equals(principal))) { |
| checkPrincipal(principal); |
| Object po = principalPermissions.get(principal); |
| if (po != null) |
| permissions = ((Integer) po).intValue(); |
| } |
| |
| return permissions | globalPermissions; |
| } |
| |
| /** |
| * Check whether the given permissions are granted to a certain principal. |
| * The requested permissions are specified as a bitfield, for example |
| * <code>(Acl.ADD | Acl.DELETE | Acl.GET)</code>. |
| * |
| * @param principal The entity to check, or "*" to check whether |
| * the given permissions are granted to all principals globally |
| * @param permissions The permissions to check |
| * @return <code>true</code> if the principal holds all the given permissions |
| * @throws IllegalArgumentException if <code>principal</code> is not a |
| * valid principal name or if <code>permissions</code> is not a |
| * valid combination of the permission constants defined in this |
| * class |
| */ |
| public synchronized boolean isPermitted(String principal, int permissions) { |
| checkPermissions(permissions); |
| |
| int hasPermissions = getPermissions(principal); |
| return (permissions & hasPermissions) == permissions; |
| } |
| |
| /** |
| * Create a new <code>Acl</code> instance from this <code>Acl</code> where |
| * all permissions for the given principal are overwritten with the given |
| * permissions. |
| * <p> |
| * Note, that when changing the permissions of a specific principal, it is |
| * not allowed to specify a set of permissions stricter than the global set |
| * of permissions (that apply to all principals). |
| * |
| * @param principal The entity to which permissions should be granted, or |
| * "*" to globally grant permissions to all principals. |
| * @param permissions The set of permissions to be given. The parameter is |
| * a bitmask of the permission constants defined in this class. |
| * @return a new <code>Acl</code> instance |
| * @throws IllegalArgumentException if <code>principal</code> is not a |
| * valid principal name, if <code>permissions</code> is not a |
| * valid combination of the permission constants defined in this |
| * class, or if a globally granted permission would have been |
| * revoked from a specific principal |
| */ |
| public synchronized Acl setPermission(String principal, int permissions) { |
| checkPermissions(permissions); |
| |
| Acl newPermission = new Acl(this, principal, permissions); |
| return newPermission; |
| } |
| |
| /** |
| * Get the list of principals who have any kind of permissions on this node. |
| * The list only includes those principals that have been explicitly |
| * assigned permissions (so "*" is never returned), globally set |
| * permissions naturally apply to all other principals as well. |
| * |
| * @return The array of principals having permissions on this node. |
| */ |
| public String[] getPrincipals() { |
| return (String[]) (principalPermissions.keySet().toArray(new String[0])); |
| } |
| |
| /** |
| * Give the canonic string representation of this ACL. The operations are in |
| * the following order: {Add, Delete, Exec, Get, Replace}, principal names |
| * are sorted alphabetically. |
| * |
| * @return The string representation as defined in OMA DM. |
| */ |
| public synchronized String toString() { |
| String acl = null; |
| for (int i = 0; i < PERMISSION_CODES.length; i++) |
| acl = writeEntry(PERMISSION_CODES[i], acl); |
| |
| return (acl != null) ? acl : ""; |
| } |
| |
| // ----- Private utility methods -----// |
| |
| private String writeEntry(int command, String acl) { |
| String aclEntry = null; |
| |
| if ((command & globalPermissions) > 0) |
| aclEntry = ALL_PRINCIPALS; |
| else { |
| // TreeMap guarantees alphabetical ordering of keys during traversal |
| Iterator i = principalPermissions.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry entry = (Map.Entry) i.next(); |
| if ((command & ((Integer) entry.getValue()).intValue()) > 0) |
| aclEntry = appendEntry(aclEntry, '+', (String) entry |
| .getKey()); |
| } |
| } |
| |
| if (aclEntry == null) |
| return acl; |
| |
| return appendEntry(acl, '&', writeCommands(command) + '=' + aclEntry); |
| } |
| |
| private static void deleteFromAll(TreeMap principalPermissions, int perm) { |
| Iterator i = principalPermissions.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry entry = (Map.Entry) i.next(); |
| setPrincipalPermission(principalPermissions, (String) entry |
| .getKey(), ((Integer) entry.getValue()).intValue() & ~perm); |
| } |
| } |
| |
| private static void setPrincipalPermission(TreeMap principalPermissions, |
| String principal, int perm) { |
| if (perm == 0) |
| principalPermissions.remove(principal); |
| else |
| principalPermissions.put(principal, new Integer(perm)); |
| } |
| |
| private static String writeCommands(int command) { |
| String commandStr = null; |
| for (int i = 0; i < PERMISSION_CODES.length; i++) |
| if ((command & PERMISSION_CODES[i]) != 0) |
| commandStr = appendEntry(commandStr, ',', PERMISSION_NAMES[i]); |
| |
| return (commandStr != null) ? commandStr : ""; |
| } |
| |
| private static String appendEntry(String base, char separator, String entry) { |
| return (base != null) ? base + separator + entry : entry; |
| } |
| |
| private static int parseCommand(String command) { |
| int i = Arrays.asList(PERMISSION_NAMES).indexOf(command); |
| if (i == -1) |
| throw new IllegalArgumentException( |
| "Invalid ACL string: unknown command '" + command + "'."); |
| |
| return PERMISSION_CODES[i]; |
| } |
| |
| private static void checkPermissions(int perm) { |
| if ((perm & ~ALL_PERMISSION) != 0) |
| throw new IllegalArgumentException("Invalid ACL permission value: " |
| + perm); |
| } |
| |
| private static void checkPrincipal(String principal) { |
| if (principal == null) |
| throw new IllegalArgumentException("Principal is null."); |
| |
| checkServerId(principal, "Principal name contains illegal character"); |
| } |
| |
| private static void checkServerId(String serverId, String errorText) { |
| char[] chars = serverId.toCharArray(); |
| for (int i = 0; i < chars.length; i++) |
| if ("*=+&".indexOf(chars[i]) != -1 |
| || Character.isWhitespace(chars[i])) |
| throw new IllegalArgumentException(errorText + " '" + chars[i] |
| + "'."); |
| } |
| |
| private static String[] split(String input, char sep, int limit) { |
| Vector v = new Vector(); |
| boolean limited = (limit > 0); |
| int applied = 0; |
| int index = 0; |
| StringBuffer part = new StringBuffer(); |
| |
| while (index < input.length()) { |
| char ch = input.charAt(index); |
| if (ch != sep) |
| part.append(ch); |
| else { |
| ++applied; |
| v.add(part.toString()); |
| part = new StringBuffer(); |
| } |
| ++index; |
| if (limited && applied == limit - 1) |
| break; |
| } |
| while (index < input.length()) { |
| char ch = input.charAt(index); |
| part.append(ch); |
| ++index; |
| } |
| v.add(part.toString()); |
| |
| int last = v.size(); |
| if (0 == limit) { |
| for (int j = v.size() - 1; j >= 0; --j) { |
| String s = (String) v.elementAt(j); |
| if ("".equals(s)) |
| --last; |
| else |
| break; |
| } |
| } |
| |
| String[] ret = new String[last]; |
| for (int i = 0; i < last; ++i) |
| ret[i] = (String) v.elementAt(i); |
| |
| return ret; |
| } |
| } |