All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.osgi.service.dmt.Acl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) OSGi Alliance (2004, 2013). 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.dmt;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

/**
 * {@code Acl} is an immutable class representing structured access to DMT ACLs.
 * Under OMA DM the ACLs are defined as strings with an internal syntax.
 * 

* 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 *} " indicating any * principal. *

* The syntax for valid remote server IDs: *

* <server-identifier> ::= All printable characters except * {@code '='}, {@code '&'}, {@code '*'}, {@code '+'} or white-space characters. * * @author $Id: a8eef2d9a9105197eb1e5e6f94013d7976a4f929 $ */ 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 canonical string representation. * * @param acl The string representation of the ACL as defined in OMA DM. If * {@code null} 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]} will hold {@code permissions[i]} in the ACL. *

* 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} 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} ACL except for the * permissions of the given {@code principal}, which are overwritten with * the given {@code permissions}. *

* Assumes that the permissions parameter has been checked. All * modifications of an {@code Acl} (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} 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} instance. * Two {@code Acl} 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} instance * @return {@code true} 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} instances * are equal according to the {@link #equals(Object)} 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} instance from this {@code Acl} 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} of more permission constants defined in this * class. * @return a new {@code Acl} instance * @throws IllegalArgumentException if {@code principal} is not a valid * principal name or if {@code permissions} 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} instance from this {@code Acl} with the given * permission revoked from the given principal. Other permissions of the * principal are not affected. *

* 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} of more permission constants defined in this * class. * @return a new {@code Acl} instance * @throws IllegalArgumentException if {@code principal} is not a valid * principal name, if {@code permissions} 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} * is a bitmask of the permission constants defined in this class * @throws IllegalArgumentException if {@code principal} 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)}. * * @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} if the principal holds all the given permissions * @throws IllegalArgumentException if {@code principal} is not a valid * principal name or if {@code permissions} 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} instance from this {@code Acl} where all * permissions for the given principal are overwritten with the given * permissions. *

* 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} instance * @throws IllegalArgumentException if {@code principal} is not a valid * principal name, if {@code permissions} 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 canonical 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 || principal.length() == 0) throw new IllegalArgumentException("Principal is null or empty."); 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy