You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2013/02/13 16:19:34 UTC

svn commit: r1445660 [1/2] - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/security/privilege/ main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/ test/java/org/apache/jackrabbit/oak/security/privilege/

Author: angela
Date: Wed Feb 13 15:19:34 2013
New Revision: 1445660

URL: http://svn.apache.org/r1445660
Log:
OAK-527: permissions (wip)
OAK-64: privilege mgt (wip)

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeBits.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionStore.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/privilege/
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeBitsTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionStoreTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidatorTest.java
Removed:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionReader.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionWriter.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/JcrAllCommitHook.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeConstants.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeInitializer.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeMigrator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidatorProvider.java
    jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/JcrAllCommitHook.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/JcrAllCommitHook.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/JcrAllCommitHook.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/JcrAllCommitHook.java Wed Feb 13 15:19:34 2013
@@ -73,6 +73,14 @@ public class JcrAllCommitHook implements
                     propertyBuilder.addValue(name);
                     jcrAll.setProperty(propertyBuilder.getPropertyState());
                 }
+
+                // update the privilege bits of the jcr:all in case the new
+                // privilege isn't an aggregate
+                if (after.getProperty(REP_AGGREGATES) == null) {
+                    PrivilegeBits bits = PrivilegeBits.getInstance(after.getProperty(REP_BITS));
+                    PrivilegeBits all = PrivilegeBits.getInstance(jcrAll.getProperty(REP_BITS));
+                    PrivilegeBits.getInstance(all).add(bits).writeTo(jcrAll, JCR_ALL);
+                }
             }
         }
 

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeBits.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeBits.java?rev=1445660&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeBits.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeBits.java Wed Feb 13 15:19:34 2013
@@ -0,0 +1,770 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.security.privilege;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.primitives.Longs;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.authorization.Permissions;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * {@code PrivilegeBits} TODO
+ */
+public final class PrivilegeBits implements PrivilegeConstants {
+
+    private static final long NO_PRIVILEGE = 0;
+    private static final long READ_NODES = 1;
+    private static final long READ_PROPERTIES = READ_NODES << 1;
+    private static final long ADD_PROPERTIES = READ_PROPERTIES << 1;
+    private static final long ALTER_PROPERTIES = ADD_PROPERTIES << 1;
+    private static final long REMOVE_PROPERTIES = ALTER_PROPERTIES << 1;
+    private static final long ADD_CHILD_NODES = REMOVE_PROPERTIES << 1;
+    private static final long REMOVE_CHILD_NODES = ADD_CHILD_NODES << 1;
+    private static final long REMOVE_NODE = REMOVE_CHILD_NODES << 1;
+    private static final long READ_AC = REMOVE_NODE << 1;
+    private static final long MODIFY_AC = READ_AC << 1;
+    private static final long NODE_TYPE_MNGMT = MODIFY_AC << 1;
+    private static final long VERSION_MNGMT = NODE_TYPE_MNGMT << 1;
+    private static final long LOCK_MNGMT = VERSION_MNGMT << 1;
+    private static final long LIFECYCLE_MNGMT = LOCK_MNGMT << 1;
+    private static final long RETENTION_MNGMT = LIFECYCLE_MNGMT << 1;
+    private static final long WORKSPACE_MNGMT = RETENTION_MNGMT << 1;
+    private static final long NODE_TYPE_DEF_MNGMT = WORKSPACE_MNGMT << 1;
+    private static final long NAMESPACE_MNGMT = NODE_TYPE_DEF_MNGMT << 1;
+    private static final long PRIVILEGE_MNGMT = NAMESPACE_MNGMT << 1;
+    private static final long USER_MNGMT = PRIVILEGE_MNGMT << 1;
+
+    private static final long READ = READ_NODES | READ_PROPERTIES;
+    private static final long MODIFY_PROPERTIES = ADD_PROPERTIES | ALTER_PROPERTIES | REMOVE_PROPERTIES;
+    private static final long WRITE = MODIFY_PROPERTIES | ADD_CHILD_NODES | REMOVE_CHILD_NODES | REMOVE_NODE;
+    private static final long WRITE2 = WRITE | NODE_TYPE_MNGMT;
+
+    public static final PrivilegeBits EMPTY = new PrivilegeBits(UnmodifiableData.EMPTY);
+
+    static final Map<String, PrivilegeBits> BUILT_IN = new HashMap<String, PrivilegeBits>();
+
+    static {
+        BUILT_IN.put(REP_READ_NODES, getInstance(READ_NODES));
+        BUILT_IN.put(REP_READ_PROPERTIES, getInstance(READ_PROPERTIES));
+        BUILT_IN.put(REP_ADD_PROPERTIES, getInstance(ADD_PROPERTIES));
+        BUILT_IN.put(REP_ALTER_PROPERTIES, getInstance(ALTER_PROPERTIES));
+        BUILT_IN.put(REP_REMOVE_PROPERTIES, getInstance(REMOVE_PROPERTIES));
+        BUILT_IN.put(JCR_ADD_CHILD_NODES, getInstance(ADD_CHILD_NODES));
+        BUILT_IN.put(JCR_REMOVE_CHILD_NODES, getInstance(REMOVE_CHILD_NODES));
+        BUILT_IN.put(JCR_REMOVE_NODE, getInstance(REMOVE_NODE));
+        BUILT_IN.put(JCR_READ_ACCESS_CONTROL, getInstance(READ_AC));
+        BUILT_IN.put(JCR_MODIFY_ACCESS_CONTROL, getInstance(MODIFY_AC));
+        BUILT_IN.put(JCR_NODE_TYPE_MANAGEMENT, getInstance(NODE_TYPE_MNGMT));
+        BUILT_IN.put(JCR_VERSION_MANAGEMENT, getInstance(VERSION_MNGMT));
+        BUILT_IN.put(JCR_LOCK_MANAGEMENT, getInstance(LOCK_MNGMT));
+        BUILT_IN.put(JCR_LIFECYCLE_MANAGEMENT, getInstance(LIFECYCLE_MNGMT));
+        BUILT_IN.put(JCR_RETENTION_MANAGEMENT, getInstance(RETENTION_MNGMT));
+        BUILT_IN.put(JCR_WORKSPACE_MANAGEMENT, getInstance(WORKSPACE_MNGMT));
+        BUILT_IN.put(JCR_NODE_TYPE_DEFINITION_MANAGEMENT, getInstance(NODE_TYPE_DEF_MNGMT));
+        BUILT_IN.put(JCR_NAMESPACE_MANAGEMENT, getInstance(NAMESPACE_MNGMT));
+        BUILT_IN.put(REP_PRIVILEGE_MANAGEMENT, getInstance(PRIVILEGE_MNGMT));
+        BUILT_IN.put(REP_USER_MANAGEMENT, getInstance(USER_MNGMT));
+
+        BUILT_IN.put(JCR_READ, PrivilegeBits.getInstance(READ));
+        BUILT_IN.put(JCR_MODIFY_PROPERTIES, PrivilegeBits.getInstance(MODIFY_PROPERTIES));
+        BUILT_IN.put(JCR_WRITE, PrivilegeBits.getInstance(WRITE));
+        BUILT_IN.put(REP_WRITE, PrivilegeBits.getInstance(WRITE2));
+    }
+
+    private final Data d;
+
+    /**
+     * Private constructor.
+     *
+     * @param d
+     */
+    private PrivilegeBits(Data d) {
+        this.d = d;
+    }
+
+    /**
+     * Creates a mutable instance of privilege bits.
+     *
+     * @return a new instance of privilege bits.
+     */
+    public static PrivilegeBits getInstance() {
+        return new PrivilegeBits(new ModifiableData());
+    }
+
+    /**
+     * Creates a mutable instance of privilege bits.
+     *
+     * @param base
+     * @return a new instance of privilege bits.
+     */
+    public static PrivilegeBits getInstance(PrivilegeBits base) {
+        return new PrivilegeBits(new ModifiableData(base.d));
+    }
+
+    /**
+     * Get or create an instance of privilege bits for a specific property that
+     * stores privileges.
+     *
+     * @param property
+     * @return an instance of {@code PrivilegeBits}
+     */
+    public static PrivilegeBits getInstance(PropertyState property) {
+        if (property == null) {
+            return EMPTY;
+        }
+
+        List<Long> vs = (List<Long>) property.getValue(Type.LONGS);
+        if (vs.size() == 1) {
+            return getInstance(vs.get(0));
+        } else {
+            return getInstance(Longs.toArray(vs));
+        }
+    }
+
+    /**
+     * Get or create an instance of privilege bits for a privilege definition.
+     *
+     * @param tree A privilege definition tree or the privileges root.
+     * @return an instance of {@code PrivilegeBits}
+     */
+    public static PrivilegeBits getInstance(Tree tree) {
+        if (tree == null) {
+            return EMPTY;
+        }
+        String privName = tree.getName();
+        if (BUILT_IN.containsKey(privName)) {
+            return BUILT_IN.get(privName);
+        } else if (REP_PRIVILEGES.equals(privName)) {
+            return getInstance(tree.getProperty(REP_NEXT));
+        } else {
+            return getInstance(tree.getProperty(REP_BITS));
+        }
+    }
+
+    /**
+     * Internal method to get or create an instance of privilege bits for the
+     * specified long value.
+     *
+     * @param bits
+     * @return an instance of {@code PrivilegeBits}
+     */
+    private static PrivilegeBits getInstance(long bits) {
+        if (bits == NO_PRIVILEGE) {
+            return EMPTY;
+        } else {
+            checkArgument(bits > NO_PRIVILEGE);
+            return new PrivilegeBits(new UnmodifiableData(bits));
+        }
+    }
+
+
+    /**
+     * Internal method to create a new instance of {@code PrivilegeBits}.
+     *
+     * @param bits
+     * @return an instance of {@code PrivilegeBits}
+     */
+    private static PrivilegeBits getInstance(long[] bits) {
+        return new PrivilegeBits(new UnmodifiableData(bits));
+    }
+
+    /**
+     * TODO
+     *
+     * @param bits
+     * @param parentBits
+     * @param isAllow
+     * @return
+     */
+    public static long calculatePermissions(PrivilegeBits bits, PrivilegeBits parentBits, boolean isAllow) {
+        long privs = bits.d.longValue();
+        long parentPrivs = parentBits.d.longValue();
+        long perm = Permissions.NO_PERMISSION;
+        if ((privs & READ) == READ) {
+            perm |= Permissions.READ;
+        } else {
+            if ((privs & READ_NODES) == READ_NODES) {
+                perm |= Permissions.READ_NODE;
+            } else if (((privs & READ_PROPERTIES) == READ_PROPERTIES)) {
+                perm |= Permissions.READ_PROPERTY;
+            }
+        }
+        if ((privs & MODIFY_PROPERTIES) == MODIFY_PROPERTIES) {
+            perm |= Permissions.SET_PROPERTY;
+        } else {
+            if ((privs & ADD_PROPERTIES) == ADD_PROPERTIES) {
+                perm |= Permissions.ADD_PROPERTY;
+            } else if ((privs & MODIFY_PROPERTIES) == MODIFY_PROPERTIES) {
+                perm |= Permissions.MODIFY_PROPERTY;
+            } else if ((privs & REMOVE_PROPERTIES) == REMOVE_PROPERTIES) {
+                perm |= Permissions.REMOVE_PROPERTY;
+            }
+        }
+
+        // add_node permission is granted through privilege on the parent.
+        if ((parentPrivs & ADD_CHILD_NODES) == ADD_CHILD_NODES) {
+            perm |= Permissions.ADD_NODE;
+        }
+
+        /*
+         remove_node is
+         allowed: only if remove_child_nodes privilege is present on
+                  the parent AND remove_node is present on the node itself
+         denied : if either remove_child_nodes is denied on the parent
+                  OR remove_node is denied on the node itself.
+        */
+        if (isAllow) {
+            if ((parentPrivs & REMOVE_CHILD_NODES) == REMOVE_CHILD_NODES &&
+                    (privs & REMOVE_NODE) == REMOVE_NODE) {
+                perm |= Permissions.REMOVE_NODE;
+            }
+        } else {
+            if ((parentPrivs & REMOVE_CHILD_NODES) == REMOVE_CHILD_NODES ||
+                    (privs & REMOVE_NODE) == REMOVE_NODE) {
+                perm |= Permissions.REMOVE_NODE;
+            }
+        }
+
+        // modify_child_node_collection permission is granted through
+        // privileges on the parent
+        if ((parentPrivs & ADD_CHILD_NODES) == ADD_CHILD_NODES &&
+                (parentPrivs & REMOVE_CHILD_NODES) == REMOVE_CHILD_NODES) {
+            perm |= Permissions.MODIFY_CHILD_NODE_COLLECTION;
+        }
+
+        // the remaining (special) permissions are simply defined on the node
+        if ((privs & READ_AC) == READ_AC) {
+            perm |= Permissions.READ_ACCESS_CONTROL;
+        }
+        if ((privs & MODIFY_AC) == MODIFY_AC) {
+            perm |= Permissions.MODIFY_ACCESS_CONTROL;
+        }
+        if ((privs & LIFECYCLE_MNGMT) == LIFECYCLE_MNGMT) {
+            perm |= Permissions.LIFECYCLE_MANAGEMENT;
+        }
+        if ((privs & LOCK_MNGMT) == LOCK_MNGMT) {
+            perm |= Permissions.LOCK_MANAGEMENT;
+        }
+        if ((privs & NODE_TYPE_MNGMT) == NODE_TYPE_MNGMT) {
+            perm |= Permissions.NODE_TYPE_MANAGEMENT;
+        }
+        if ((privs & RETENTION_MNGMT) == RETENTION_MNGMT) {
+            perm |= Permissions.RETENTION_MANAGEMENT;
+        }
+        if ((privs & VERSION_MNGMT) == VERSION_MNGMT) {
+            perm |= Permissions.VERSION_MANAGEMENT;
+        }
+        if ((privs & WORKSPACE_MNGMT) == WORKSPACE_MNGMT) {
+            perm |= Permissions.WORKSPACE_MANAGEMENT;
+        }
+        if ((privs & NODE_TYPE_DEF_MNGMT) == NODE_TYPE_DEF_MNGMT) {
+            perm |= Permissions.NODE_TYPE_DEFINITION_MANAGEMENT;
+        }
+        if ((privs & NAMESPACE_MNGMT) == NAMESPACE_MNGMT) {
+            perm |= Permissions.NAMESPACE_MANAGEMENT;
+        }
+        if ((privs & PRIVILEGE_MNGMT) == PRIVILEGE_MNGMT) {
+            perm |= Permissions.PRIVILEGE_MANAGEMENT;
+        }
+        if ((privs & USER_MNGMT) == USER_MNGMT) {
+            perm |= Permissions.USER_MANAGEMENT;
+        }
+        return perm;
+    }
+
+    /**
+     * Returns {@code true} if this privilege bits includes no privileges
+     * at all.
+     *
+     * @return {@code true} if this privilege bits includes no privileges
+     *         at all; {@code false} otherwise.
+     * @see Permissions#NO_PERMISSION
+     */
+    public boolean isEmpty() {
+        return d.isEmpty();
+    }
+
+    /**
+     * Returns an unmodifiable instance.
+     *
+     * @return an unmodifiable {@code PrivilegeBits} instance.
+     */
+    public PrivilegeBits unmodifiable() {
+        if (d instanceof ModifiableData) {
+            if (d.isSimple()) {
+                return getInstance(d.longValue());
+            } else {
+                long[] bits = d.longValues();
+                long[] copy = new long[bits.length];
+                System.arraycopy(bits, 0, copy, 0, bits.length);
+                return getInstance(copy);
+            }
+        } else {
+            return this;
+        }
+    }
+
+    /**
+     * Returns {@code true} if all privileges defined by the specified
+     * {@code otherBits} are present in this instance.
+     *
+     * @param otherBits
+     * @return {@code true} if all privileges defined by the specified
+     *         {@code otherBits} are included in this instance; {@code false}
+     *         otherwise.
+     */
+    public boolean includes(PrivilegeBits otherBits) {
+        return d.includes(otherBits.d);
+    }
+
+    /**
+     * Returns {@code true} if this instance includes the jcr:read
+     * privilege. Shortcut for calling {@link PrivilegeBits#includes(PrivilegeBits)}
+     * where the other bits represented the jcr:read privilege.
+     *
+     * @return {@code true} if this instance includes the jcr:read
+     *         privilege; {@code false} otherwise.
+     */
+    public boolean includesRead(long readPermission) {
+        if (this == EMPTY) {
+            return false;
+        } else {
+            return d.includes(readPermission);
+        }
+    }
+
+    /**
+     * Adds the other privilege bits to this instance.
+     *
+     * @param other The other privilege bits to be added.
+     * @return The updated instance.
+     * @throws UnsupportedOperationException if this instance is immutable.
+     */
+    public PrivilegeBits add(PrivilegeBits other) {
+        if (d instanceof ModifiableData) {
+            ((ModifiableData) d).add(other.d);
+            return this;
+        } else {
+            throw new UnsupportedOperationException("immutable privilege bits");
+        }
+    }
+
+    /**
+     * Subtracts the other PrivilegeBits from the this.<br>
+     * If the specified bits do not intersect with this, it isn't modified.<br>
+     * If {@code this} is included in {@code other} {@link #EMPTY empty}
+     * privilege bits is returned.
+     *
+     * @param other The other privilege bits to be substracted from this instance.
+     * @return The updated instance.
+     * @throws UnsupportedOperationException if this instance is immutable.
+     */
+    public PrivilegeBits diff(PrivilegeBits other) {
+        if (d instanceof ModifiableData) {
+            ((ModifiableData) d).diff(other.d);
+            return this;
+        } else {
+            throw new UnsupportedOperationException("immutable privilege bits");
+        }
+    }
+
+    /**
+     * Subtracts the {@code b} from {@code a} and adds the result (diff)
+     * to this instance.
+     *
+     * @param a An instance of privilege bits.
+     * @param b An instance of privilege bits.
+     * @return The updated instance.
+     * @throws UnsupportedOperationException if this instance is immutable.
+     */
+    public PrivilegeBits addDifference(PrivilegeBits a, PrivilegeBits b) {
+        if (d instanceof ModifiableData) {
+            ((ModifiableData) d).addDifference(a.d, b.d);
+            return this;
+        } else {
+            throw new UnsupportedOperationException("immutable privilege bits");
+        }
+    }
+
+    /**
+     * Package private method to calculate the privilege bits associated with a
+     * given built-in or custom privilege definition.
+     *
+     * @return an instance of {@code PrivilegeBits}
+     */
+    PrivilegeBits nextBits() {
+        if (this == EMPTY) {
+            return EMPTY;
+        } else {
+            return new PrivilegeBits(d.next());
+        }
+    }
+
+    void writeTo(Tree tree) {
+        String name = (REP_PRIVILEGES.equals(tree.getName())) ? REP_NEXT : REP_BITS;
+        tree.setProperty(name, Longs.asList(d.longValues()), Type.LONGS);
+    }
+
+    void writeTo(NodeBuilder nodeBuilder, String nodeName) {
+        String name = (REP_PRIVILEGES.equals(nodeName)) ? REP_NEXT : REP_BITS;
+        nodeBuilder.setProperty(name, Longs.asList(d.longValues()), Type.LONGS);
+    }
+
+    //-------------------------------------------------------------< Object >---
+    @Override
+    public int hashCode() {
+        return d.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (o instanceof PrivilegeBits) {
+            return d.equals(((PrivilegeBits) o).d);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("PrivilegeBits: ");
+        if (d.isSimple()) {
+            sb.append(d.longValue());
+        } else {
+            sb.append(Arrays.toString(d.longValues()));
+        }
+        return sb.toString();
+    }
+
+    //------------------------------------------------------< inner classes >---
+
+    /**
+     * Base class for the internal privilege bits representation and handling.
+     */
+    private static abstract class Data {
+
+        abstract boolean isEmpty();
+
+        abstract long longValue();
+
+        abstract long[] longValues();
+
+        abstract boolean isSimple();
+
+        abstract Data next();
+
+        abstract boolean includes(Data other);
+
+        abstract boolean includes(long permissions);
+
+        static boolean includes(long bits, long otherBits) {
+            return (bits | ~otherBits) == -1;
+        }
+
+        static boolean includes(long[] bits, long[] otherBits) {
+            if (otherBits.length <= bits.length) {
+                // test for each long if is included
+                for (int i = 0; i < otherBits.length; i++) {
+                    if ((bits[i] | ~otherBits[i]) != -1) {
+                        return false;
+                    }
+                }
+                return true;
+            } else {
+                // otherbits array is longer > cannot be included in bits
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Immutable Data object
+     */
+    private static class UnmodifiableData extends Data {
+
+        private static final long MAX = Long.MAX_VALUE / 2;
+
+        private static final UnmodifiableData EMPTY = new UnmodifiableData(NO_PRIVILEGE);
+
+        private final long bits;
+        private final long[] bitsArr;
+        private final boolean isSimple;
+
+        private UnmodifiableData(long bits) {
+            this.bits = bits;
+            bitsArr = new long[]{bits};
+            isSimple = true;
+        }
+
+        private UnmodifiableData(long[] bitsArr) {
+            bits = NO_PRIVILEGE;
+            this.bitsArr = bitsArr;
+            isSimple = false;
+        }
+
+        @Override
+        boolean isEmpty() {
+            return this == EMPTY;
+        }
+
+        @Override
+        long longValue() {
+            return bits;
+        }
+
+        @Override
+        long[] longValues() {
+            return bitsArr;
+        }
+
+        @Override
+        boolean isSimple() {
+            return isSimple;
+        }
+
+        @Override
+        Data next() {
+            if (this == EMPTY) {
+                return EMPTY;
+            } else if (isSimple) {
+                if (bits < MAX) {
+                    long b = bits << 1;
+                    return new UnmodifiableData(b);
+                } else {
+                    return new UnmodifiableData(new long[]{bits}).next();
+                }
+            } else {
+                long[] bts;
+                long last = bitsArr[bitsArr.length - 1];
+                if (last < MAX) {
+                    bts = new long[bitsArr.length];
+                    System.arraycopy(bitsArr, 0, bts, 0, bitsArr.length);
+                    bts[bts.length - 1] = last << 1;
+                } else {
+                    bts = new long[bitsArr.length + 1];
+                    bts[bts.length - 1] = 1;
+                }
+                return new UnmodifiableData(bts);
+            }
+        }
+
+        @Override
+        boolean includes(Data other) {
+            if (isSimple) {
+                return (other.isSimple()) && includes(bits, other.longValue());
+            } else {
+                return includes(bitsArr, other.longValues());
+            }
+        }
+
+        @Override
+        boolean includes(long permissions) {
+            return (isSimple) ? (bits & permissions) == permissions : (bitsArr[0] & permissions) == permissions;
+        }
+
+        //---------------------------------------------------------< Object >---
+        @Override
+        public int hashCode() {
+            return (isSimple) ? new Long(bits).hashCode() : Arrays.hashCode(bitsArr);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            } else if (o instanceof UnmodifiableData) {
+                UnmodifiableData d = (UnmodifiableData) o;
+                if (isSimple != d.isSimple) {
+                    return false;
+                }
+                if (isSimple) {
+                    return bits == d.bits;
+                } else {
+                    return Arrays.equals(bitsArr, d.bitsArr);
+                }
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Mutable implementation of the Data base class.
+     */
+    private static class ModifiableData extends Data {
+
+        private long[] bits;
+
+        private ModifiableData() {
+            bits = new long[]{NO_PRIVILEGE};
+        }
+
+        private ModifiableData(Data base) {
+            long[] b = base.longValues();
+            switch (b.length) {
+                case 0:
+                    // empty
+                    bits = new long[]{NO_PRIVILEGE};
+                    break;
+                case 1:
+                    // single long
+                    bits = new long[]{b[0]};
+                    break;
+                default:
+                    // copy
+                    bits = new long[b.length];
+                    System.arraycopy(b, 0, bits, 0, b.length);
+            }
+        }
+
+        @Override
+        boolean isEmpty() {
+            return bits.length == 1 && bits[0] == NO_PRIVILEGE;
+        }
+
+        @Override
+        long longValue() {
+            return (bits.length == 1) ? bits[0] : NO_PRIVILEGE;
+        }
+
+        @Override
+        long[] longValues() {
+            return bits;
+        }
+
+        @Override
+        boolean isSimple() {
+            return bits.length == 1;
+        }
+
+        @Override
+        Data next() {
+            throw new UnsupportedOperationException("Not implemented.");
+        }
+
+        @Override
+        boolean includes(Data other) {
+            if (bits.length == 1) {
+                return other.isSimple() && includes(bits[0], other.longValue());
+            } else {
+                return includes(bits, other.longValues());
+            }
+        }
+
+        @Override
+        boolean includes(long permissions) {
+            return (bits[0] & permissions) == permissions;
+        }
+
+        /**
+         * Add the other Data to this instance.
+         *
+         * @param other
+         */
+        private void add(Data other) {
+            if (other != this) {
+                if (bits.length == 1 && other.isSimple()) {
+                    bits[0] |= other.longValue();
+                } else {
+                    or(other.longValues());
+                }
+            }
+        }
+
+        /**
+         * Subtract the other Data from this instance.
+         *
+         * @param other
+         */
+        private void diff(Data other) {
+            if (bits.length == 1 && other.isSimple()) {
+                bits[0] = bits[0] & ~other.longValue();
+            } else {
+                bits = diff(bits, other.longValues());
+            }
+        }
+
+        /**
+         * Add the diff between the specified Data a and b.
+         *
+         * @param a
+         * @param b
+         */
+        private void addDifference(Data a, Data b) {
+            if (a.isSimple() && b.isSimple()) {
+                bits[0] |= a.longValue() & ~b.longValue();
+            } else {
+                long[] diff = diff(a.longValues(), b.longValues());
+                or(diff);
+            }
+        }
+
+        private void or(long[] b) {
+            if (b.length > bits.length) {
+                // enlarge the array
+                long[] res = new long[b.length];
+                System.arraycopy(bits, 0, res, 0, bits.length);
+                bits = res;
+            }
+            for (int i = 0; i < b.length; i++) {
+                bits[i] |= b[i];
+            }
+        }
+
+        private static long[] diff(long[] a, long[] b) {
+            int index = -1;
+            long[] res = new long[((a.length > b.length) ? a.length : b.length)];
+            for (int i = 0; i < res.length; i++) {
+                if (i < a.length && i < b.length) {
+                    res[i] = a[i] & ~b[i];
+                } else {
+                    res[i] = (i < a.length) ? a[i] : 0;
+                }
+                // remember start of trailing 0 array entries
+                if (res[i] != 0) {
+                    index = -1;
+                } else if (index == -1) {
+                    index = i;
+                }
+            }
+            switch (index) {
+                case -1:
+                    // no need to remove trailing 0-long from the array
+                    return res;
+                case 0:
+                    // array consisting of one or multiple 0
+                    return new long[]{NO_PRIVILEGE};
+                default:
+                    // remove trailing 0-long entries from the array
+                    long[] r2 = new long[index];
+                    System.arraycopy(res, 0, r2, 0, index);
+                    return r2;
+            }
+        }
+    }
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeConstants.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeConstants.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeConstants.java Wed Feb 13 15:19:34 2013
@@ -26,96 +26,165 @@ import org.apache.jackrabbit.JcrConstant
  */
 public interface PrivilegeConstants {
 
-    /** Internal (oak) name for the root node of the privilege store. */
+    /**
+     * Internal (oak) name for the root node of the privilege store.
+     */
     String REP_PRIVILEGES = "rep:privileges";
-    /** Internal (oak) path for the privilege store.*/
+    /**
+     * Internal (oak) path for the privilege store.
+     */
     String PRIVILEGES_PATH = '/' + JcrConstants.JCR_SYSTEM + '/' + REP_PRIVILEGES;
 
-    /** Node type name of the root node of the privilege store */
+    /**
+     * Node type name of the root node of the privilege store
+     */
     String NT_REP_PRIVILEGES = "rep:Privileges";
-    /** Node type name of the privilege definition nodes */
+    /**
+     * Node type name of the privilege definition nodes
+     */
     String NT_REP_PRIVILEGE = "rep:Privilege";
 
-    /** Name of the property that defines if the privilege is abstract. */
+    /**
+     * Name of the property that defines if the privilege is abstract.
+     */
     String REP_IS_ABSTRACT = "rep:isAbstract";
-    /** Name of the privilege definition property that stores the aggregate privilege names. */
+    /**
+     * Name of the privilege definition property that stores the aggregate privilege names.
+     */
     String REP_AGGREGATES = "rep:aggregates";
+    /**
+     * TODO
+     */
+    String REP_NEXT = "rep:next";
+    /**
+     * TODO
+     */
+    String REP_BITS = "rep:bits";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_READ} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_READ} privilege
+     */
     String JCR_READ = "jcr:read";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_MODIFY_PROPERTIES} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_MODIFY_PROPERTIES} privilege
+     */
     String JCR_MODIFY_PROPERTIES = "jcr:modifyProperties";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_ADD_CHILD_NODES} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_ADD_CHILD_NODES} privilege
+     */
     String JCR_ADD_CHILD_NODES = "jcr:addChildNodes";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_REMOVE_NODE} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_REMOVE_NODE} privilege
+     */
     String JCR_REMOVE_NODE = "jcr:removeNode";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_REMOVE_CHILD_NODES} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_REMOVE_CHILD_NODES} privilege
+     */
     String JCR_REMOVE_CHILD_NODES = "jcr:removeChildNodes";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_WRITE} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_WRITE} privilege
+     */
     String JCR_WRITE = "jcr:write";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_READ_ACCESS_CONTROL} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_READ_ACCESS_CONTROL} privilege
+     */
     String JCR_READ_ACCESS_CONTROL = "jcr:readAccessControl";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_MODIFY_ACCESS_CONTROL} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_MODIFY_ACCESS_CONTROL} privilege
+     */
     String JCR_MODIFY_ACCESS_CONTROL = "jcr:modifyAccessControl";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_LOCK_MANAGEMENT} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_LOCK_MANAGEMENT} privilege
+     */
     String JCR_LOCK_MANAGEMENT = "jcr:lockManagement";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_VERSION_MANAGEMENT} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_VERSION_MANAGEMENT} privilege
+     */
     String JCR_VERSION_MANAGEMENT = "jcr:versionManagement";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_NODE_TYPE_MANAGEMENT} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_NODE_TYPE_MANAGEMENT} privilege
+     */
     String JCR_NODE_TYPE_MANAGEMENT = "jcr:nodeTypeManagement";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_RETENTION_MANAGEMENT} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_RETENTION_MANAGEMENT} privilege
+     */
     String JCR_RETENTION_MANAGEMENT = "jcr:retentionManagement";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_LIFECYCLE_MANAGEMENT} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_LIFECYCLE_MANAGEMENT} privilege
+     */
     String JCR_LIFECYCLE_MANAGEMENT = "jcr:lifecycleManagement";
 
-    /** Internal (oak) name of the jcr:workspaceManagement privilege */
+    /**
+     * Internal (oak) name of the jcr:workspaceManagement privilege
+     */
     String JCR_WORKSPACE_MANAGEMENT = "jcr:workspaceManagement";
 
-    /** Internal (oak) name of the jcr:nodeTypeDefinitionManagement privilege */
+    /**
+     * Internal (oak) name of the jcr:nodeTypeDefinitionManagement privilege
+     */
     String JCR_NODE_TYPE_DEFINITION_MANAGEMENT = "jcr:nodeTypeDefinitionManagement";
 
-    /** Internal (oak) name of the jcr:namespaceManagement privilege */
+    /**
+     * Internal (oak) name of the jcr:namespaceManagement privilege
+     */
     String JCR_NAMESPACE_MANAGEMENT = "jcr:namespaceManagement";
 
-    /** Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_ALL} privilege */
+    /**
+     * Internal (oak) name of the {@link javax.jcr.security.Privilege#JCR_ALL} privilege
+     */
     String JCR_ALL = "jcr:all";
 
-    /** Internal (oak) name of the rep:privilegeManagement privilege */
+    /**
+     * Internal (oak) name of the rep:privilegeManagement privilege
+     */
     String REP_PRIVILEGE_MANAGEMENT = "rep:privilegeManagement";
     /**
      * Internal (oak) name of the rep:userManagement privilege
+     *
      * @since OAK 1.0
      */
     String REP_USER_MANAGEMENT = "rep:userManagement";
 
-    /** Internal (oak) name of the rep:write privilege */
+    /**
+     * Internal (oak) name of the rep:write privilege
+     */
     String REP_WRITE = "rep:write";
 
-    /** Internal (oak) name of the rep:readNodes privilege */
+    /**
+     * Internal (oak) name of the rep:readNodes privilege
+     */
     String REP_READ_NODES = "rep:readNodes";
 
-    /** Internal (oak) name of the rep:readProperties privilege */
+    /**
+     * Internal (oak) name of the rep:readProperties privilege
+     */
     String REP_READ_PROPERTIES = "rep:readProperties";
 
-    /** Internal (oak) name of the rep:addProperties privilege */
+    /**
+     * Internal (oak) name of the rep:addProperties privilege
+     */
     String REP_ADD_PROPERTIES = "rep:addProperties";
 
-    /** Internal (oak) name of the rep:alterProperties privilege */
+    /**
+     * Internal (oak) name of the rep:alterProperties privilege
+     */
     String REP_ALTER_PROPERTIES = "rep:alterProperties";
 
-    /** Internal (oak) name of the rep:removeProperties privilege */
+    /**
+     * Internal (oak) name of the rep:removeProperties privilege
+     */
     String REP_REMOVE_PROPERTIES = "rep:removeProperties";
 
     /**
@@ -123,4 +192,4 @@ public interface PrivilegeConstants {
      * the {@link #NT_REP_PRIVILEGE rep:Privilege} node type
      */
     Set<String> PRIVILEGE_PROPERTY_NAMES = ImmutableSet.of(REP_IS_ABSTRACT, REP_AGGREGATES);
-}
\ No newline at end of file
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionStore.java?rev=1445660&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionStore.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeDefinitionStore.java Wed Feb 13 15:19:34 2013
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.oak.security.privilege;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeDefinition;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reads and writes privilege definitions from and to the repository content
+ * without applying any validation.
+ */
+public class PrivilegeDefinitionStore implements PrivilegeConstants {
+
+    private static final Logger log = LoggerFactory.getLogger(PrivilegeDefinitionStore.class);
+
+    /**
+     * The internal names of all built-in privileges that are not aggregates.
+     */
+    private static final String[] NON_AGGR_PRIVILEGES = new String[]{
+            REP_READ_NODES, REP_READ_PROPERTIES,
+            REP_ADD_PROPERTIES, REP_ALTER_PROPERTIES, REP_REMOVE_PROPERTIES,
+            JCR_ADD_CHILD_NODES, JCR_REMOVE_CHILD_NODES, JCR_REMOVE_NODE,
+            JCR_READ_ACCESS_CONTROL, JCR_MODIFY_ACCESS_CONTROL, JCR_NODE_TYPE_MANAGEMENT,
+            JCR_VERSION_MANAGEMENT, JCR_LOCK_MANAGEMENT, JCR_LIFECYCLE_MANAGEMENT,
+            JCR_RETENTION_MANAGEMENT, JCR_WORKSPACE_MANAGEMENT, JCR_NODE_TYPE_DEFINITION_MANAGEMENT,
+            JCR_NAMESPACE_MANAGEMENT, REP_PRIVILEGE_MANAGEMENT, REP_USER_MANAGEMENT};
+
+    /**
+     * The internal names and aggregation definition of all built-in privileges
+     * that are aggregates (except for jcr:all).
+     */
+    private static final Map<String, String[]> AGGREGATE_PRIVILEGES = ImmutableMap.of(
+            JCR_READ, new String[]{REP_READ_NODES, REP_READ_PROPERTIES},
+            JCR_MODIFY_PROPERTIES, new String[]{REP_ADD_PROPERTIES, REP_ALTER_PROPERTIES, REP_REMOVE_PROPERTIES},
+            JCR_WRITE, new String[]{JCR_MODIFY_PROPERTIES, JCR_ADD_CHILD_NODES, JCR_REMOVE_CHILD_NODES, JCR_REMOVE_NODE},
+            REP_WRITE, new String[]{JCR_WRITE, JCR_NODE_TYPE_MANAGEMENT});
+
+    private final Root root;
+    private final Map<PrivilegeBits, Set<String>> bitsToNames = new HashMap<PrivilegeBits, Set<String>>();
+
+    private PrivilegeBits next;
+
+    public PrivilegeDefinitionStore(Root root) {
+        this.root = root;
+        Tree privilegesTree = getPrivilegesTree();
+        if (privilegesTree != null && privilegesTree.hasProperty(REP_NEXT)) {
+            next = PrivilegeBits.getInstance(privilegesTree);
+        } else {
+            next = PrivilegeBits.BUILT_IN.get(REP_USER_MANAGEMENT).nextBits();
+        }
+    }
+
+    /**
+     * Returns the root tree for all privilege definitions stored in the content
+     * repository.
+     *
+     * @return The privileges root.
+     */
+    @CheckForNull
+    Tree getPrivilegesTree() {
+        return root.getTree(PRIVILEGES_PATH);
+    }
+
+    /**
+     * @param privilegeNames
+     * @return
+     */
+    @Nonnull
+    public PrivilegeBits getBits(@Nonnull String... privilegeNames) {
+        if (privilegeNames.length == 0) {
+            return PrivilegeBits.EMPTY;
+        }
+
+        Tree privilegesTree = getPrivilegesTree();
+        if (privilegesTree == null) {
+            return PrivilegeBits.EMPTY;
+        }
+        PrivilegeBits bits = PrivilegeBits.getInstance();
+        for (String privilegeName : privilegeNames) {
+            Tree defTree = privilegesTree.getChild(privilegeName);
+            if (defTree != null) {
+                bits.add(PrivilegeBits.getInstance(defTree));
+            }
+        }
+        return bits.unmodifiable();
+    }
+
+    /**
+     * Resolve the given privilege bits to a set of privilege names.
+     *
+     * @param privilegeBits An instance of privilege bits.
+     * @return The names of the registed privileges associated with the given
+     *         bits. Any bits that don't have a corresponding privilege definition will
+     *         be ignored.
+     */
+    @Nonnull
+    public Set<String> getPrivilegeNames(PrivilegeBits privilegeBits) {
+        if (privilegeBits == null || privilegeBits.isEmpty()) {
+            return Collections.emptySet();
+        } else if (bitsToNames.containsKey(privilegeBits)) {
+            // matches all built-in aggregates and single built-in privileges
+            return bitsToNames.get(privilegeBits);
+        } else {
+            Tree privilegesTree = getPrivilegesTree();
+            if (privilegesTree == null) {
+                return Collections.emptySet();
+            }
+
+            if (bitsToNames.isEmpty()) {
+                for (Tree child : privilegesTree.getChildren()) {
+                    bitsToNames.put(PrivilegeBits.getInstance(child), Collections.singleton(child.getName()));
+                }
+            }
+
+            Set<String> privilegeNames;
+            if (bitsToNames.containsKey(privilegeBits)) {
+                privilegeNames = bitsToNames.get(privilegeBits);
+            } else {
+                privilegeNames = new HashSet<String>();
+                Set<String> aggregates = new HashSet<String>();
+                for (Tree child : privilegesTree.getChildren()) {
+                    PrivilegeBits bits = PrivilegeBits.getInstance(child);
+                    if (privilegeBits.includes(bits)) {
+                        privilegeNames.add(child.getName());
+                        if (child.hasProperty(REP_AGGREGATES)) {
+                            aggregates.addAll(readDefinition(child).getDeclaredAggregateNames());
+                        }
+                    }
+                }
+                privilegeNames.removeAll(aggregates);
+                bitsToNames.put(privilegeBits.unmodifiable(), ImmutableSet.copyOf(privilegeNames));
+            }
+            return privilegeNames;
+        }
+    }
+
+    /**
+     * Read all registered privilege definitions from the content.
+     *
+     * @return All privilege definitions stored in the content.
+     */
+    @Nonnull
+    public Map<String, PrivilegeDefinition> readDefinitions() {
+        Tree privilegesTree = getPrivilegesTree();
+        if (privilegesTree == null) {
+            return Collections.emptyMap();
+        } else {
+            Map<String, PrivilegeDefinition> definitions = new HashMap<String, PrivilegeDefinition>();
+            for (Tree child : privilegesTree.getChildren()) {
+                PrivilegeDefinition def = readDefinition(child);
+                definitions.put(def.getName(), def);
+            }
+            return definitions;
+        }
+    }
+
+    /**
+     * Retrieve the privilege definition with the specified {@code privilegeName}.
+     *
+     * @param privilegeName The name of a registered privilege definition.
+     * @return The privilege definition with the specified name or {@code null}
+     *         if the name doesn't refer to a registered privilege.
+     */
+    @CheckForNull
+    public PrivilegeDefinition readDefinition(String privilegeName) {
+        Tree privilegesTree = getPrivilegesTree();
+        if (privilegesTree == null) {
+            return null;
+        } else {
+            Tree definitionTree = privilegesTree.getChild(privilegeName);
+            return (definitionTree == null) ? null : readDefinition(definitionTree);
+        }
+    }
+
+    /**
+     * @param definitionTree
+     * @return
+     */
+    @Nonnull
+    static PrivilegeDefinition readDefinition(@Nonnull Tree definitionTree) {
+        String name = definitionTree.getName();
+        boolean isAbstract = TreeUtil.getBoolean(definitionTree, REP_IS_ABSTRACT);
+        String[] declAggrNames = TreeUtil.getStrings(definitionTree, REP_AGGREGATES);
+
+        return new PrivilegeDefinitionImpl(name, isAbstract, declAggrNames);
+    }
+
+    /**
+     * Write the given privilege definition to the repository content.
+     *
+     * @param definition The new privilege definition.
+     * @throws RepositoryException If the definition can't be written.
+     */
+    public void writeDefinition(PrivilegeDefinition definition) throws RepositoryException {
+        writeDefinitions(Collections.singleton(definition));
+    }
+
+    /**
+     * Create the built-in privilege definitions during repository setup.
+     *
+     * @throws RepositoryException If an error occurs.
+     */
+    void writeBuiltInDefinitions() throws RepositoryException {
+        writeDefinitions(getBuiltInDefinitions());
+    }
+
+    //------------------------------------------------------------< private >---
+
+    /**
+     * @param definitions
+     * @throws RepositoryException
+     */
+    private void writeDefinitions(Iterable<PrivilegeDefinition> definitions) throws RepositoryException {
+        try {
+            // make sure the privileges path is defined
+            Tree privilegesTree = getPrivilegesTree();
+            if (privilegesTree == null) {
+                throw new RepositoryException("Privilege store does not exist.");
+            }
+            NodeUtil privilegesNode = new NodeUtil(getPrivilegesTree());
+            for (PrivilegeDefinition definition : definitions) {
+                if (privilegesNode.hasChild(definition.getName())) {
+                    throw new RepositoryException("Privilege definition with name '" + definition.getName() + "' already exists.");
+                }
+                writePrivilegeNode(privilegesNode, definition);
+            }
+            /*
+            update the property storing the next privilege bits with the
+            privileges root tree. this is a cheap way to detect collisions that
+            may arise from concurrent registration of custom privileges.
+            */
+            next.writeTo(privilegesTree);
+
+            // delegate validation to the commit validation (see above)
+            root.commit();
+
+        } catch (CommitFailedException e) {
+            Throwable t = e.getCause();
+            if (t instanceof RepositoryException) {
+                throw (RepositoryException) t;
+            } else {
+                throw new RepositoryException(e);
+            }
+        }
+    }
+
+    private void writePrivilegeNode(NodeUtil privilegesNode, PrivilegeDefinition definition) {
+        String name = definition.getName();
+        NodeUtil privNode = privilegesNode.addChild(name, NT_REP_PRIVILEGE);
+        if (definition.isAbstract()) {
+            privNode.setBoolean(REP_IS_ABSTRACT, true);
+        }
+        String[] declAggrNames = definition.getDeclaredAggregateNames().toArray(new String[definition.getDeclaredAggregateNames().size()]);
+        boolean isAggregate = declAggrNames.length > 0;
+        if (isAggregate) {
+            privNode.setNames(REP_AGGREGATES, declAggrNames);
+        }
+
+        PrivilegeBits bits;
+        if (PrivilegeBits.BUILT_IN.containsKey(name)) {
+            bits = PrivilegeBits.BUILT_IN.get(name);
+        } else if (isAggregate) {
+            bits = getBits(declAggrNames);
+        } else {
+            bits = next;
+            next = bits.nextBits();
+        }
+        bits.writeTo(privNode.getTree());
+    }
+
+    private static Collection<PrivilegeDefinition> getBuiltInDefinitions() {
+        Map<String, PrivilegeDefinition> definitions = new LinkedHashMap<String, PrivilegeDefinition>();
+        for (String privilegeName : NON_AGGR_PRIVILEGES) {
+            PrivilegeDefinition def = new PrivilegeDefinitionImpl(privilegeName, false);
+            definitions.put(privilegeName, def);
+        }
+        for (String privilegeName : AGGREGATE_PRIVILEGES.keySet()) {
+            PrivilegeDefinition def = new PrivilegeDefinitionImpl(privilegeName, false, AGGREGATE_PRIVILEGES.get(privilegeName));
+            definitions.put(privilegeName, def);
+        }
+        PrivilegeDefinition all = new PrivilegeDefinitionImpl(JCR_ALL, false, definitions.keySet());
+        definitions.put(JCR_ALL, all);
+        return definitions.values();
+    }
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeInitializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeInitializer.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeInitializer.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeInitializer.java Wed Feb 13 15:19:34 2013
@@ -16,13 +16,9 @@
  */
 package org.apache.jackrabbit.oak.security.privilege;
 
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
 
 import javax.jcr.RepositoryException;
 
-import com.google.common.collect.ImmutableMap;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Type;
@@ -30,7 +26,6 @@ import org.apache.jackrabbit.oak.core.Ro
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants;
 import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
-import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeDefinition;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
@@ -48,24 +43,6 @@ class PrivilegeInitializer implements Re
 
     private static final Logger log = LoggerFactory.getLogger(PrivilegeInitializer.class);
 
-    /** The internal names of all built-in privileges that are not aggregates. */
-    private static final String[] NON_AGGR_PRIVILEGES = new String[] {
-            REP_READ_NODES, REP_READ_PROPERTIES,
-            REP_ADD_PROPERTIES, REP_ALTER_PROPERTIES, REP_REMOVE_PROPERTIES,
-            JCR_ADD_CHILD_NODES, JCR_REMOVE_CHILD_NODES, JCR_REMOVE_NODE,
-            JCR_READ_ACCESS_CONTROL, JCR_MODIFY_ACCESS_CONTROL, JCR_NODE_TYPE_MANAGEMENT,
-            JCR_VERSION_MANAGEMENT, JCR_LOCK_MANAGEMENT, JCR_LIFECYCLE_MANAGEMENT,
-            JCR_RETENTION_MANAGEMENT, JCR_WORKSPACE_MANAGEMENT, JCR_NODE_TYPE_DEFINITION_MANAGEMENT,
-            JCR_NAMESPACE_MANAGEMENT, REP_PRIVILEGE_MANAGEMENT, REP_USER_MANAGEMENT};
-
-    /** The internal names and aggregation definition of all built-in privileges
-        that are aggregates (except for jcr:all). */
-    private static final Map<String, String[]> AGGREGATE_PRIVILEGES = ImmutableMap.of(
-            JCR_READ, new String[] {REP_READ_NODES, REP_READ_PROPERTIES},
-            JCR_MODIFY_PROPERTIES, new String[] {REP_ADD_PROPERTIES, REP_ALTER_PROPERTIES, REP_REMOVE_PROPERTIES},
-            JCR_WRITE, new String[] {JCR_MODIFY_PROPERTIES, JCR_ADD_CHILD_NODES, JCR_REMOVE_CHILD_NODES, JCR_REMOVE_NODE},
-            REP_WRITE, new String[] {JCR_WRITE, JCR_NODE_TYPE_MANAGEMENT});
-
     @Override
     public NodeState initialize(NodeState state) {
         NodeBuilder root = state.builder();
@@ -86,9 +63,8 @@ class PrivilegeInitializer implements Re
                 throw new RuntimeException(e);
             }
 
-            PrivilegeDefinitionWriter writer = new PrivilegeDefinitionWriter(new RootImpl(store));
             try {
-                writer.writeDefinitions(getBuiltInDefinitions());
+                new PrivilegeDefinitionStore(new RootImpl(store)).writeBuiltInDefinitions();
             } catch (RepositoryException e) {
                 log.error("Failed to register built-in privileges", e);
                 throw new RuntimeException(e);
@@ -97,19 +73,4 @@ class PrivilegeInitializer implements Re
         }
         return root.getNodeState();
     }
-
-    private Collection<PrivilegeDefinition> getBuiltInDefinitions() {
-        Map<String, PrivilegeDefinition> definitions = new LinkedHashMap<String, PrivilegeDefinition>();
-        for (String privilegeName : NON_AGGR_PRIVILEGES) {
-            PrivilegeDefinition def = new PrivilegeDefinitionImpl(privilegeName, false);
-            definitions.put(privilegeName, def);
-        }
-        for (String privilegeName : AGGREGATE_PRIVILEGES.keySet()) {
-            PrivilegeDefinition def = new PrivilegeDefinitionImpl(privilegeName, false, AGGREGATE_PRIVILEGES.get(privilegeName));
-            definitions.put(privilegeName, def);
-        }
-        PrivilegeDefinition all = new PrivilegeDefinitionImpl(JCR_ALL, false, definitions.keySet());
-        definitions.put(JCR_ALL, all);
-        return definitions.values();
-    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeManagerImpl.java Wed Feb 13 15:19:34 2013
@@ -81,9 +81,9 @@ public class PrivilegeManagerImpl implem
         if (privilegeName == null || privilegeName.isEmpty()) {
             throw new RepositoryException("Invalid privilege name " + privilegeName);
         }
-        PrivilegeDefinition definition = new PrivilegeDefinitionImpl(getOakName(privilegeName), isAbstract, getOakNames(declaredAggregateNames));
-        PrivilegeDefinitionWriter writer = new PrivilegeDefinitionWriter(getWriteRoot());
-        writer.writeDefinition(definition);
+        PrivilegeDefinitionImpl definition = new PrivilegeDefinitionImpl(getOakName(privilegeName), isAbstract, getOakNames(declaredAggregateNames));
+        PrivilegeDefinitionStore store = getStore(getWriteRoot());
+        store.writeDefinition(definition);
 
         // refresh the current root to make sure the definition is visible
         root.refresh();
@@ -130,18 +130,18 @@ public class PrivilegeManagerImpl implem
 
     @Nonnull
     private PrivilegeDefinition[] getPrivilegeDefinitions() {
-        Map<String, PrivilegeDefinition> definitions = getReader().readDefinitions();
+        Map<String, PrivilegeDefinition> definitions = getStore(root).readDefinitions();
         return definitions.values().toArray(new PrivilegeDefinition[definitions.size()]);
     }
 
     @CheckForNull
     private PrivilegeDefinition getPrivilegeDefinition(String oakName) {
-        return getReader().readDefinition(oakName);
+        return getStore(root).readDefinition(oakName);
     }
 
     @Nonnull
-    private PrivilegeDefinitionReader getReader() {
-        return new PrivilegeDefinitionReader(root);
+    private static PrivilegeDefinitionStore getStore(Root targetRoot) {
+        return new PrivilegeDefinitionStore(targetRoot);
     }
 
     //--------------------------------------------------------------------------

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeMigrator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeMigrator.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeMigrator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeMigrator.java Wed Feb 13 15:19:34 2013
@@ -48,7 +48,7 @@ import org.xml.sax.helpers.DefaultHandle
 /**
  * PrivilegeMigrator is a utility to migrate custom privilege definitions from
  * a jackrabbit 2 project to oak.
- *
+ * <p/>
  * TODO: this is an initial draft of a migration tool from jr2 custom privileges
  * TODO: to oak. might need to be adjusted once we have defined a upgrade path (see OAK-458)
  */
@@ -62,7 +62,7 @@ public class PrivilegeMigrator {
 
     public void migrateCustomPrivileges(InputStream privilegeStream) throws RepositoryException {
         final Root root = contentSession.getLatestRoot();
-        PrivilegeDefinitionWriter writer = new PrivilegeDefinitionWriter(root);
+        PrivilegeDefinitionStore store = new PrivilegeDefinitionStore(root);
         try {
             NamespaceRegistry nsRegistry = new ReadWriteNamespaceRegistry() {
                 @Override
@@ -75,8 +75,9 @@ public class PrivilegeMigrator {
                     return root.getTree("/");
                 }
             };
-            Iterable<PrivilegeDefinition> custom = readCustomDefinitons(privilegeStream, nsRegistry);
-            writer.writeDefinitions(custom);
+            for (PrivilegeDefinition def : readCustomDefinitons(privilegeStream, nsRegistry)) {
+                store.writeDefinition(def);
+            }
         } catch (IOException e) {
             throw new RepositoryException(e);
         }
@@ -95,7 +96,7 @@ public class PrivilegeMigrator {
      * @throws IOException
      */
     private static Iterable<PrivilegeDefinition> readCustomDefinitons(InputStream customPrivileges,
-                                                                 NamespaceRegistry nsRegistry) throws RepositoryException, IOException {
+                                                                      NamespaceRegistry nsRegistry) throws RepositoryException, IOException {
         Map<String, PrivilegeDefinition> definitions = new LinkedHashMap<String, PrivilegeDefinition>();
         InputSource src = new InputSource(customPrivileges);
         for (PrivilegeDefinition def : PrivilegeXmlHandler.readDefinitions(src, nsRegistry)) {
@@ -109,6 +110,7 @@ public class PrivilegeMigrator {
     }
 
     //--------------------------------------------------------------------------
+
     /**
      * The {@code PrivilegeXmlHandler} loads privilege definitions from a XML
      * document using the following format:
@@ -175,7 +177,8 @@ public class PrivilegeMigrator {
 
         /**
          * Build a new {@code PrivilegeDefinition} from the given XML node.
-         * @param n the xml node storing the privilege definition.
+         *
+         * @param n          the xml node storing the privilege definition.
          * @param nsRegistry
          * @return a new PrivilegeDefinition.
          * @throws javax.jcr.RepositoryException

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidator.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidator.java Wed Feb 13 15:19:34 2013
@@ -33,6 +33,8 @@ import org.apache.jackrabbit.oak.spi.com
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeDefinition;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Validator implementation that is responsible for validating any modifications
@@ -40,11 +42,14 @@ import org.apache.jackrabbit.util.Text;
  */
 class PrivilegeValidator implements PrivilegeConstants, Validator {
 
-    private final Map<String, PrivilegeDefinition> definitions;
+    private static final Logger log = LoggerFactory.getLogger(PrivilegeValidator.class);
 
-    PrivilegeValidator(NodeState before) {
-        PrivilegeDefinitionReader reader = new PrivilegeDefinitionReader(new ReadOnlyRoot(before));
-        definitions = reader.readDefinitions();
+    private final PrivilegeDefinitionStore storeBefore;
+    private final PrivilegeDefinitionStore storeAfter;
+
+    PrivilegeValidator(NodeState before, NodeState after) {
+        storeBefore = new PrivilegeDefinitionStore(new ReadOnlyRoot(before));
+        storeAfter = new PrivilegeDefinitionStore((new ReadOnlyRoot(after)));
     }
 
     //----------------------------------------------------------< Validator >---
@@ -55,7 +60,11 @@ class PrivilegeValidator implements Priv
 
     @Override
     public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException {
-        throw new CommitFailedException("Attempt to modify existing privilege definition.");
+        if (REP_NEXT.equals(before.getName())) {
+            validateNext(PrivilegeBits.getInstance(storeBefore.getPrivilegesTree().getProperty(REP_NEXT)));
+        } else {
+            throw new CommitFailedException("Attempt to modify existing privilege definition.");
+        }
     }
 
     @Override
@@ -65,6 +74,7 @@ class PrivilegeValidator implements Priv
 
     @Override
     public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
+        checkInitialized();
         // the following characteristics are expected to be validated elsewhere:
         // - permission to allow privilege registration -> permission validator.
         // - name collisions (-> delegated to NodeTypeValidator since sms are not allowed)
@@ -84,8 +94,7 @@ class PrivilegeValidator implements Priv
         }
 
         // additional validation of the definition
-        PrivilegeDefinition def = PrivilegeDefinitionReader.readDefinition(tree);
-        validateDefinition(def);
+        validateDefinition(tree);
 
         // privilege definitions may not have child nodes.
         return null;
@@ -102,29 +111,65 @@ class PrivilegeValidator implements Priv
     }
 
     //------------------------------------------------------------< private >---
+    private void checkInitialized() throws CommitFailedException {
+        if (storeBefore.getPrivilegesTree() == null) {
+            throw new CommitFailedException("Privilege store not initialized.");
+        }
+    }
+
+    private void validateNext(PrivilegeBits bits) throws CommitFailedException {
+        PrivilegeBits next = PrivilegeBits.getInstance(storeAfter.getPrivilegesTree().getProperty(REP_NEXT));
+        if (!next.equals(bits.nextBits())) {
+            throw new CommitFailedException("Next bits not updated.");
+        }
+    }
+
+    private void validateBits(PrivilegeBits bits, PrivilegeBits expectedNext) throws CommitFailedException {
+        if (!expectedNext.equals(bits.nextBits())) {
+            throw new CommitFailedException("PrivilegeBits violation: Expected " + expectedNext + "; Found" + bits + '.');
+        }
+    }
 
     /**
      * Validation of the privilege definition including the following steps:
      * <p/>
+     * - privilege bits must not collide with an existing privilege
+     * - next bits must have been adjusted in case of a non-aggregate privilege
      * - all aggregates must have been registered before
      * - no existing privilege defines the same aggregation
      * - no cyclic aggregation
      *
-     * @param definition The new privilege definition to validate.
+     * @param definitionTree The new privilege definition tree to validate.
      * @throws org.apache.jackrabbit.oak.api.CommitFailedException
      *          If any of
      *          the checks listed above fails.
      */
-    private void validateDefinition(PrivilegeDefinition definition) throws CommitFailedException {
+    private void validateDefinition(Tree definitionTree) throws CommitFailedException {
+        PrivilegeBits newBits = PrivilegeBits.getInstance(definitionTree);
+        if (newBits.isEmpty()) {
+            throw new CommitFailedException("PrivilegeBits are missing.");
+        }
+
+        Set<String> privNames = storeBefore.getPrivilegeNames(newBits);
+        PrivilegeDefinition definition = PrivilegeDefinitionStore.readDefinition(definitionTree);
         Set<String> declaredNames = definition.getDeclaredAggregateNames();
+
+        // non-aggregate privilege
         if (declaredNames.isEmpty()) {
+            if (!privNames.isEmpty()) {
+                throw new CommitFailedException("PrivilegeBits already in used.");
+            }
+            validateNext(newBits);
             return;
         }
 
+        // aggregation of a single privilege
         if (declaredNames.size() == 1) {
             throw new CommitFailedException("Singular aggregation is equivalent to existing privilege.");
         }
 
+        // aggregation of >1 privileges
+        Map<String, PrivilegeDefinition> definitions = storeBefore.readDefinitions();
         for (String aggrName : declaredNames) {
             // aggregated privilege not registered
             if (!definitions.containsKey(aggrName)) {
@@ -132,13 +177,13 @@ class PrivilegeValidator implements Priv
             }
 
             // check for circular aggregation
-            if (isCircularAggregation(definition.getName(), aggrName)) {
+            if (isCircularAggregation(definition.getName(), aggrName, definitions)) {
                 String msg = "Detected circular aggregation within custom privilege caused by " + aggrName;
                 throw new CommitFailedException(msg);
             }
         }
 
-        Set<String> aggregateNames = resolveAggregates(declaredNames);
+        Set<String> aggregateNames = resolveAggregates(declaredNames, definitions);
         for (PrivilegeDefinition existing : definitions.values()) {
             Set<String> existingDeclared = existing.getDeclaredAggregateNames();
             if (existingDeclared.isEmpty()) {
@@ -146,14 +191,20 @@ class PrivilegeValidator implements Priv
             }
 
             // test for exact same aggregation or aggregation with the same net effect
-            if (declaredNames.equals(existingDeclared) || aggregateNames.equals(resolveAggregates(existingDeclared))) {
+            if (declaredNames.equals(existingDeclared) || aggregateNames.equals(resolveAggregates(existingDeclared, definitions))) {
                 String msg = "Custom aggregate privilege '" + definition.getName() + "' is already covered by '" + existing.getName() + '\'';
                 throw new CommitFailedException(msg);
             }
         }
+
+        PrivilegeBits aggrBits = storeBefore.getBits(declaredNames.toArray(new String[declaredNames.size()]));
+        if (!newBits.equals(aggrBits)) {
+            throw new CommitFailedException("Invalid privilege bits for aggregated privilege definition.");
+        }
     }
 
-    private boolean isCircularAggregation(String privilegeName, String aggregateName) {
+    private static boolean isCircularAggregation(String privilegeName, String aggregateName,
+                                                 Map<String, PrivilegeDefinition> definitions) {
         if (privilegeName.equals(aggregateName)) {
             return true;
         }
@@ -168,14 +219,14 @@ class PrivilegeValidator implements Priv
                     return true;
                 }
                 if (definitions.containsKey(name)) {
-                    isCircular = isCircularAggregation(privilegeName, name);
+                    isCircular = isCircularAggregation(privilegeName, name, definitions);
                 }
             }
             return isCircular;
         }
     }
 
-    private Set<String> resolveAggregates(Set<String> declared) throws CommitFailedException {
+    private static Set<String> resolveAggregates(Set<String> declared, Map<String, PrivilegeDefinition> definitions) throws CommitFailedException {
         Set<String> aggregateNames = new HashSet<String>();
         for (String name : declared) {
             PrivilegeDefinition d = definitions.get(name);
@@ -187,7 +238,7 @@ class PrivilegeValidator implements Priv
             if (names.isEmpty()) {
                 aggregateNames.add(name);
             } else {
-                aggregateNames.addAll(resolveAggregates(names));
+                aggregateNames.addAll(resolveAggregates(names, definitions));
             }
         }
         return aggregateNames;

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidatorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidatorProvider.java?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidatorProvider.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/privilege/PrivilegeValidatorProvider.java Wed Feb 13 15:19:34 2013
@@ -36,6 +36,6 @@ class PrivilegeValidatorProvider impleme
     @Nonnull
     @Override
     public Validator getRootValidator(NodeState before, NodeState after) {
-        return new SubtreeValidator(new PrivilegeValidator(before), JCR_SYSTEM, REP_PRIVILEGES);
+        return new SubtreeValidator(new PrivilegeValidator(before, after), JCR_SYSTEM, REP_PRIVILEGES);
     }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd?rev=1445660&r1=1445659&r2=1445660&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd Wed Feb 13 15:19:34 2013
@@ -615,7 +615,7 @@
  */
 [rep:Permissions]
   - rep:accessControlledPath (PATH) protected mandatory
-  - rep:privilegeBits (UNDEFINED) protected mandatory
+  - rep:privileges (UNDEFINED) protected mandatory
   - rep:index (LONG) protected mandatory
   - * (UNDEFINED) protected
 
@@ -661,7 +661,7 @@
 [rep:Members]
   orderable
   + * (rep:Members) = rep:Members protected multiple /* FIXME: SNS definition */
-  - * (WEAKREFERENCE) protected < 'rep:Authorizable' /* Deprecated since OAK */
+  - * (WEAKREFERENCE) protected < 'rep:Authorizable' /* Deprecated since oak 1.0 */
   - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable' /* @since oak 1.0 */
 
 // -----------------------------------------------------------------------------
@@ -673,6 +673,7 @@
  */
 [rep:Privileges]
   + * (rep:Privilege) = rep:Privilege protected ABORT
+  - rep:next (LONG) protected multiple mandatory
 
 /**
  * @since oak 1.0
@@ -680,6 +681,7 @@
 [rep:Privilege]
   - rep:isAbstract (BOOLEAN) protected
   - rep:aggregates (NAME) protected multiple
+  - rep:bits (LONG) protected multiple mandatory
 
 // -----------------------------------------------------------------------------
 // Token Management