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     * &quot;.&quot;, or by itself, to signify a wildcard match. For example:
038     * &quot;org.osgi.security.protocol.*&quot; or &quot;*&quot; is valid, but
039     * &quot;*protocol&quot; or &quot;a*b&quot; are not valid.
040     * 
041     * <p>
042     * The <code>UserAdminPermission</code> with the reserved name &quot;admin&quot;
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 &quot;admin&quot;)
080     *  (org.osgi.service.useradmin.UserAdminPermission &quot;com.foo.*&quot; &quot;changeProperty,getCredential,changeCredential&quot;)
081     *  (org.osgi.service.useradmin.UserAdminPermission &quot;user.*&quot;, &quot;changeProperty,changeCredential&quot;)
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 &quot;${jars}http.jar&quot; {
107     *    permission org.osgi.service.useradmin.UserAdminPermission
108     *      &quot;user.password&quot;, &quot;getCredential&quot;;
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 &quot;admin&quot;.
125             */
126            public static final String      ADMIN                                           = "admin";
127            /**
128             * The action string &quot;changeProperty&quot;.
129             */
130            public static final String      CHANGE_PROPERTY                         = "changeProperty";
131            private static final int        ACTION_CHANGE_PROPERTY          = 0x1;
132            /**
133             * The action string &quot;changeCredential&quot;.
134             */
135            public static final String      CHANGE_CREDENTIAL                       = "changeCredential";
136            private static final int        ACTION_CHANGE_CREDENTIAL        = 0x2;
137            /**
138             * The action string &quot;getCredential&quot;.
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             * &quot;admin&quot; 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             *         &quot;admin&quot; 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             * &quot;implies&quot; 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             * &quot;java.*&quot; implies &quot;java.home&quot;.
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    }