Author: angela
Date: Tue Aug 10 09:58:03 2010
New Revision: 983906
URL: http://svn.apache.org/viewvc?rev=983906&view=rev
Log:
JCR-2700 : Allow for wildcard restriction in resource-based ACEs
Added:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java (contents, props changed)
- copied, changed from r954954, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/GlobPatternTest.java (contents, props changed)
- copied, changed from r954954, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPatternTest.java
Removed:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPatternTest.java
Modified:
jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlConstants.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractWriteTest.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/TestAll.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateTest.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryTest.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadTest.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntryTest.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/principalbased/TestAll.java
Modified: jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java (original)
+++ jackrabbit/trunk/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java Tue Aug 10 09:58:03 2010
@@ -16,6 +16,7 @@
*/
package org.apache.jackrabbit.api.security;
+import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.security.AccessControlEntry;
@@ -36,8 +37,9 @@ public interface JackrabbitAccessControl
* Return the names of the restrictions present with this access control entry.
*
* @return the names of the restrictions
+ * @throws RepositoryException if an error occurs.
*/
- String[] getRestrictionNames();
+ String[] getRestrictionNames() throws RepositoryException;
/**
* Return the value of the restriction with the specified name or
@@ -47,6 +49,7 @@ public interface JackrabbitAccessControl
* {@link #getRestrictionNames()}.
* @return value of the restriction with the specified name or
* <code>null</code> if no such restriction exists
+ * @throws RepositoryException if an error occurs.
*/
- Value getRestriction(String restrictionName);
+ Value getRestriction(String restrictionName) throws RepositoryException;
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlConstants.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlConstants.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlConstants.java Tue Aug 10 09:58:03 2010
@@ -51,6 +51,12 @@ public interface AccessControlConstants
*/
Name P_PRINCIPAL_NAME = NF.create(Name.NS_REP_URI, "principalName");
+ /**
+ * rep:glob property name used to restrict the number of child nodes
+ * or properties that are affected by an ACL inherited from a parent node.
+ */
+ Name P_GLOB = NF.create(Name.NS_REP_URI, "glob");
+
//----------------------------------------------------< node type names >---
/**
* rep:AccessControllable nodetype
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java Tue Aug 10 09:58:03 2010
@@ -16,18 +16,21 @@
*/
package org.apache.jackrabbit.core.security.authorization;
-import java.security.Principal;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.value.ValueHelper;
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
-import org.apache.jackrabbit.value.ValueHelper;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
/**
* Simple, immutable implementation of the
@@ -61,12 +64,7 @@ public abstract class AccessControlEntry
* Jackrabbit specific extension: the list of additional restrictions to be
* included in the evaluation.
*/
- private final Map<String, Value> restrictions;
-
- /**
- * Value factory
- */
- private final ValueFactory valueFactory;
+ private final Map<Name, Value> restrictions;
/**
* Hash code being calculated on demand.
@@ -79,10 +77,11 @@ public abstract class AccessControlEntry
* @param principal Principal for this access control entry.
* @param privileges Privileges for this access control entry.
* @throws AccessControlException if either principal or privileges are invalid.
+ * @throws RepositoryException if another error occurs.
*/
protected AccessControlEntryImpl(Principal principal, Privilege[] privileges)
- throws AccessControlException {
- this(principal, privileges, true, null, null);
+ throws AccessControlException, RepositoryException {
+ this(principal, privileges, true, null);
}
/**
@@ -95,13 +94,12 @@ public abstract class AccessControlEntry
* @param restrictions A map of restriction name (String) to restriction
* (Value). See {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlList#getRestrictionNames()}
* and {@link org.apache.jackrabbit.api.security.JackrabbitAccessControlList#getRestrictionType(String)}.
- * @param valueFactory the value factory.
* @throws AccessControlException if either principal or privileges are invalid.
+ * @throws RepositoryException if another error occurs.
*/
protected AccessControlEntryImpl(Principal principal, Privilege[] privileges,
- boolean isAllow, Map<String, Value> restrictions,
- ValueFactory valueFactory)
- throws AccessControlException {
+ boolean isAllow, Map<String, Value> restrictions)
+ throws AccessControlException, RepositoryException {
if (principal == null) {
throw new IllegalArgumentException();
}
@@ -115,17 +113,48 @@ public abstract class AccessControlEntry
this.privileges = privileges;
this.privilegeBits = PrivilegeRegistry.getBits(privileges);
this.allow = isAllow;
- this.valueFactory = valueFactory;
-
+
if (restrictions == null) {
this.restrictions = Collections.emptyMap();
} else {
- this.restrictions = new HashMap<String, Value>(restrictions.size());
+ this.restrictions = new HashMap<Name, Value>(restrictions.size());
// validate the passed restrictions and fill the map
for (String key : restrictions.keySet()) {
Value value = restrictions.get(key);
- value = ValueHelper.copy(value, valueFactory);
- this.restrictions.put(key, value);
+ value = ValueHelper.copy(value, getValueFactory());
+ this.restrictions.put(getResolver().getQName(key), value);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param base
+ * @param privileges
+ * @param isAllow
+ * @throws AccessControlException
+ */
+ protected AccessControlEntryImpl(AccessControlEntryImpl base, Privilege[] privileges, boolean isAllow) throws AccessControlException {
+ // make sure no abstract privileges are passed.
+ for (Privilege privilege : privileges) {
+ if (privilege.isAbstract()) {
+ throw new AccessControlException("Privilege " + privilege + " is abstract.");
+ }
+ }
+ this.principal = base.principal;
+ this.privileges = privileges;
+ this.privilegeBits = PrivilegeRegistry.getBits(privileges);
+ this.allow = isAllow;
+
+ if (base.restrictions == null) {
+ this.restrictions = Collections.emptyMap();
+ } else {
+ this.restrictions = new HashMap<Name, Value>(base.restrictions.size());
+ // validate the passed restrictions and fill the map
+ for (Name name : restrictions.keySet()) {
+ Value value = restrictions.get(name);
+ value = ValueHelper.copy(value, getValueFactory());
+ this.restrictions.put(name, value);
}
}
}
@@ -138,6 +167,33 @@ public abstract class AccessControlEntry
}
/**
+ * Returns the restrictions defined for this entry.
+ *
+ * @return the restrictions defined for this entry.
+ */
+ public Map<Name,Value> getRestrictions() {
+ return Collections.unmodifiableMap(restrictions);
+ }
+
+ /**
+ * @param restrictionName
+ * @return The restriction with the specified name or <code>null</code>.
+ */
+ public Value getRestriction(Name restrictionName) {
+ return ValueHelper.copy(restrictions.get(restrictionName), getValueFactory());
+ }
+
+ /**
+ * @return Returns the name resolver used to convert JCR names to Name and vice versa.
+ */
+ protected abstract NameResolver getResolver();
+
+ /**
+ * @return The value factory to be used.
+ */
+ protected abstract ValueFactory getValueFactory();
+
+ /**
* Build the hash code.
*
* @return the hash code.
@@ -178,19 +234,21 @@ public abstract class AccessControlEntry
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry#getRestrictionNames()
*/
- public String[] getRestrictionNames() {
- return restrictions.keySet().toArray(new String[restrictions.size()]);
+ public String[] getRestrictionNames() throws NamespaceException {
+ String[] restrNames = new String[restrictions.size()];
+ int i = 0;
+ for (Name n : restrictions.keySet()) {
+ restrNames[i] = getResolver().getJCRName(n);
+ i++;
+ }
+ return restrNames;
}
/**
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry#getRestriction(String)
*/
- public Value getRestriction(String restrictionName) {
- if (restrictions.containsKey(restrictionName)) {
- return ValueHelper.copy(restrictions.get(restrictionName), valueFactory);
- } else {
- return null;
- }
+ public Value getRestriction(String restrictionName) throws RepositoryException {
+ return getRestriction(getResolver().getQName(restrictionName));
}
//-------------------------------------------------------------< Object >---
Copied: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java (from r954954, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java)
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java?p2=jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java&p1=jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java&r1=954954&r2=983906&rev=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java Tue Aug 10 09:58:03 2010
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jackrabbit.core.security.authorization.principalbased;
+package org.apache.jackrabbit.core.security.authorization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,57 +24,99 @@ import javax.jcr.Item;
import javax.jcr.RepositoryException;
/**
- * <code>GlobPattern</code>...
+ * <code>GlobPattern</code> defines a simplistic pattern matching. It consists
+ * of a mandatory (leading) path and an optional "glob" that may contain one or
+ * more wildcard characters ("<code>*</code>") according to the glob matching
+ * defined by {@link javax.jcr.Node#getNodes(String[])}. In contrast to that
+ * method the <code>GlobPattern</code> operates on path (not only names).
+ * <p/>
+ *
+ * <p>
+ * Please note the following special cases:
+ * <pre>
+ * NodePath | Restriction | Matches
+ * -----------------------------------------------------------------------------
+ * /foo | null | matches /foo and all children of /foo
+ * /foo | "" | matches /foo only
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * Examples including wildcard char:
+ * <pre>
+ * NodePath = "/foo"
+ * Restriction | Matches
+ * -----------------------------------------------------------------------------
+ * * | all siblings of foo and foo's and the siblings' descendants
+ * /*cat | all children of /foo whose path ends with "cat"
+ * /*/cat | all non-direct descendants of /foo named "cat"
+ * /cat* | all descendant path of /foo that have the direct foo-descendant segment starting with "cat"
+ * *cat | all siblings and descendants of foo that have a name ending with cat
+ * */cat | all descendants of /foo and foo's siblings that have a name segment "cat"
+ * cat/* | all descendants of '/foocat'
+ * /cat/* | all descendants of '/foo/cat'
+ * *cat/* | all descendants of /foo that have an intermediate segment ending with 'cat'
+ * </pre>
+ * </p>
*/
-class GlobPattern {
-
- //TODO: add proper impl.
+public final class GlobPattern {
private static Logger log = LoggerFactory.getLogger(GlobPattern.class);
- private static final char ALL = '*';
- public static final String WILDCARD_ALL = "*";
-
- private final String pattern;
- private final char[] patternChars;
-
- private final boolean matchesAll;
- private final boolean containsWildcard;
-
- private GlobPattern(String pattern) {
- this.pattern = pattern;
+ private static final char WILDCARD_CHAR = '*';
+ private static final String WILDCARD_STRING = "*";
- matchesAll = WILDCARD_ALL.equals(pattern);
- containsWildcard = pattern.indexOf(ALL) > -1;
+ private final String nodePath;
+ private final String restriction;
+
+ private final Pattern pattern;
- patternChars = pattern.toCharArray();
+ private GlobPattern(String nodePath, String restriction) {
+ if (nodePath == null) {
+ throw new IllegalArgumentException();
+ }
+ this.nodePath = nodePath;
+ this.restriction = restriction;
+
+ if (restriction != null && restriction.length() > 0) {
+ StringBuilder b = new StringBuilder(nodePath);
+ b.append(restriction);
+
+ int lastPos = restriction.lastIndexOf(WILDCARD_CHAR);
+ if (lastPos >= 0) {
+ String end;
+ if (lastPos != restriction.length()-1) {
+ end = restriction.substring(lastPos + 1);
+ } else {
+ end = null;
+ }
+ pattern = new WildcardPattern(b.toString(), end);
+ } else {
+ pattern = new PathPattern(b.toString());
+ }
+ } else {
+ pattern = new PathPattern();
+ }
}
- static GlobPattern create(String pattern) {
- if (pattern == null) {
- throw new IllegalArgumentException();
- }
- return new GlobPattern(pattern);
+ public static GlobPattern create(String nodePath, String restrictions) {
+ return new GlobPattern(nodePath, restrictions);
}
- boolean matches(String toMatch) {
- // shortcut
- if (matchesAll) {
- return true;
- }
+ public static GlobPattern create(String nodePath) {
+ return create(nodePath, null);
+ }
+
+ public boolean matches(String toMatch) {
if (toMatch == null) {
return false;
- }
-
- if (containsWildcard) {
- return matches(patternChars, toMatch.toCharArray());
} else {
- return Text.isDescendantOrEqual(pattern, toMatch);
+ return pattern.matches(toMatch);
}
}
- boolean matches(Item itemToMatch) {
+ public boolean matches(Item itemToMatch) {
try {
// TODO: missing proper impl
return matches(itemToMatch.getPath());
@@ -84,28 +126,17 @@ class GlobPattern {
}
}
- private static boolean matches(char[] patternChars, char[] toMatch) {
- // TODO: add proper impl
- for (int i = 0; i < patternChars.length; i++) {
- if (patternChars[i] == ALL) {
- return true;
- }
- if (i >= toMatch.length || patternChars[i] != toMatch[i]) {
- return false;
- }
- }
-
- return false;
- }
-
//-------------------------------------------------------------< Object >---
-
/**
* @see Object#hashCode()
*/
@Override
public int hashCode() {
- return pattern.hashCode();
+ int h = 37 * 17 + nodePath.hashCode();
+ if (restriction != null) {
+ h = 37 * h + restriction.hashCode();
+ }
+ return h;
}
/**
@@ -113,7 +144,7 @@ class GlobPattern {
*/
@Override
public String toString() {
- return pattern;
+ return nodePath + " : " + restriction;
}
/**
@@ -125,8 +156,134 @@ class GlobPattern {
return true;
}
if (obj instanceof GlobPattern) {
- return pattern.equals(((GlobPattern)obj).pattern);
+ GlobPattern other = (GlobPattern) obj;
+ return nodePath.equals(other.nodePath) &&
+ (restriction == null) ? other.restriction == null : restriction.equals(other.restriction);
}
return false;
}
+
+ //------------------------------------------------------< inner classes >---
+ /**
+ * Base for PathPattern and WildcardPattern
+ */
+ private abstract class Pattern {
+ abstract boolean matches(String toMatch);
+ }
+
+ /**
+ * Path pattern: The restriction is missing or doesn't contain any wildcard character.
+ */
+ private final class PathPattern extends Pattern {
+
+ private final String patternStr;
+
+ private PathPattern() {
+ this(null);
+ }
+ private PathPattern(String patternStr) {
+ this.patternStr = patternStr;
+ }
+
+ @Override
+ boolean matches(String toMatch) {
+ if (restriction == null) {
+ return Text.isDescendantOrEqual(nodePath, toMatch);
+ } else if (restriction.length() == 0) {
+ return nodePath.equals(toMatch);
+ } else {
+ // no wildcard contained in restriction: use path defined
+ // by nodePath + restriction to calculate the match
+ return Text.isDescendantOrEqual(patternStr, toMatch);
+ }
+ }
+ }
+
+ /**
+ * Wildcard pattern: The specified restriction contains one or more wildcard character(s).
+ */
+ private final class WildcardPattern extends Pattern {
+
+ private final String patternEnd;
+ private final char[] patternChars;
+
+ private WildcardPattern(String patternStr, String patternEnd) {
+ patternChars = patternStr.toCharArray();
+ this.patternEnd = patternEnd;
+ }
+
+ @Override
+ boolean matches(String toMatch) {
+ if (patternEnd != null && !toMatch.endsWith(patternEnd)) {
+ // shortcut: verify if end of pattern matches end of toMatch
+ return false;
+ }
+ // shortcut didn't reveal mismatch -> need to process the internal match method.
+ return matches(patternChars, 0, toMatch.toCharArray(), 0);
+ }
+
+ /**
+ *
+ * @param pattern The pattern
+ * @param pOff
+ * @param s
+ * @param sOff
+ * @return <code>true</code> if matches, <code>false</code> otherwise
+ */
+ private boolean matches(char[] pattern, int pOff,
+ char[] s, int sOff) {
+
+ /*
+ NOTE: code has been copied (and slightly modified) from
+ ChildrenCollectorFilter#internalMatches.
+ TODO: move them to a common utility.
+ */
+
+ int pLength = pattern.length;
+ int sLength = s.length;
+
+ while (true) {
+ // end of pattern reached: matches only if sOff points at the end
+ // of the string to match.
+ if (pOff >= pLength) {
+ return sOff >= sLength;
+ }
+
+ // the end of the string to match has been reached but pattern
+ // doesn't have '*' at patternIndex -> no match
+ if (sOff >= sLength && pattern[pOff] != WILDCARD_CHAR) {
+ return false;
+ }
+
+ // the next character of the pattern is '*'
+ // -> recursively test if the rest of the specified string matches
+ if (pattern[pOff] == WILDCARD_CHAR) {
+ if (++pOff >= pLength) {
+ return true;
+ }
+
+ while (true) {
+ if (matches(pattern, pOff, s, sOff)) {
+ return true;
+ }
+ if (sOff >= sLength) {
+ return false;
+ }
+ sOff++;
+ }
+ }
+
+ // not yet reached end of patter nor string and not wildcard character.
+ // the 2 strings don't match in case the characters at the current
+ // position are not the same.
+ if (pOff < pLength && sOff < sLength) {
+ if (pattern[pOff] != s[sOff]) {
+ return false;
+ }
+ }
+ pOff++;
+ sOff++;
+ }
+ }
+ }
}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java
------------------------------------------------------------------------------
svn:keywords = author date id revision url
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java Tue Aug 10 09:58:03 2010
@@ -16,13 +16,13 @@
*/
package org.apache.jackrabbit.core.security.authorization.acl;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.ProtectedItemModifier;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
+import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
@@ -47,6 +47,7 @@ import javax.jcr.security.AccessControlE
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.Privilege;
import java.security.Principal;
+import java.util.Set;
/**
* <code>ACLEditor</code>...
@@ -133,7 +134,7 @@ public class ACLEditor extends Protected
String mixin = session.getJCRName(NT_REP_ACCESS_CONTROLLABLE);
if (controlledNode.isNodeType(mixin) || controlledNode.canAddMixin(mixin)) {
acl = new ACLTemplate(nodePath, session.getPrincipalManager(),
- privilegeRegistry, session.getValueFactory());
+ privilegeRegistry, session.getValueFactory(), session);
}
} // else: acl already present -> getPolicies must be used.
return (acl != null) ? new AccessControlPolicy[] {acl} : new AccessControlPolicy[0];
@@ -171,7 +172,7 @@ public class ACLEditor extends Protected
AccessControlEntry[] entries = ((ACLTemplate) policy).getAccessControlEntries();
for (AccessControlEntry entry : entries) {
- JackrabbitAccessControlEntry ace = (JackrabbitAccessControlEntry) entry;
+ AccessControlEntryImpl ace = (AccessControlEntryImpl) entry;
Name nodeName = getUniqueNodeName(aclNode, ace.isAllow() ? "allow" : "deny");
Name ntName = (ace.isAllow()) ? NT_REP_GRANT_ACE : NT_REP_DENY_ACE;
@@ -188,6 +189,13 @@ public class ACLEditor extends Protected
Privilege[] pvlgs = ace.getPrivileges();
Value[] names = getPrivilegeNames(pvlgs, vf);
setProperty(aceNode, P_PRIVILEGES, names);
+
+ // store the restrictions:
+ Set<Name> restrNames = ace.getRestrictions().keySet();
+ for (Name restrName : restrNames) {
+ Value value = ace.getRestriction(restrName);
+ setProperty(aceNode, restrName, value);
+ }
}
// mark the parent modified.
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java Tue Aug 10 09:58:03 2010
@@ -310,6 +310,7 @@ public class ACLProvider extends Abstrac
*
* @param node the Node to collect the ACLs for, which must NOT be part of the
* structure defined by mix:AccessControllable.
+ * @param permissions
* @param acls List used to collect the effective acls.
* @throws RepositoryException if an error occurs
*/
@@ -382,7 +383,7 @@ public class ACLProvider extends Abstrac
/**
* Test if the given node is access controlled. The node is access
- * controlled if it is of nodetype
+ * controlled if it is of node type
* {@link AccessControlConstants#NT_REP_ACCESS_CONTROLLABLE "rep:AccessControllable"}
* and if it has a child node named
* {@link AccessControlConstants#N_POLICY "rep:ACL"}.
@@ -403,7 +404,8 @@ public class ACLProvider extends Abstrac
private class AclPermissions extends AbstractCompiledPermissions implements AccessControlListener {
private final List<String> principalNames;
- private final Map<NodeId, Boolean> readCache = new LRUMap(1000);
+ // TODO find optimal cache size and ev. make it configurable (see also JCR-2573).
+ private final Map<ItemId, Boolean> readCache = new LRUMap(5000);
private final Object monitor = new Object();
private AclPermissions(Set<Principal> principals) throws RepositoryException {
@@ -425,7 +427,7 @@ public class ACLProvider extends Abstrac
}
}
- private Result buildResult(NodeImpl node, boolean existingNode, boolean isAcItem, EntryFilter filter) throws RepositoryException {
+ private Result buildResult(NodeImpl node, boolean isExistingNode, boolean isAcItem, EntryFilterImpl filter) throws RepositoryException {
// retrieve all ACEs at path or at the direct ancestor of path that
// apply for the principal names.
Iterator<AccessControlEntry> entries = retrieveResultEntries(getNode(node, isAcItem), filter);
@@ -444,17 +446,22 @@ public class ACLProvider extends Abstrac
int parentAllows = PrivilegeRegistry.NO_PRIVILEGE;
int parentDenies = PrivilegeRegistry.NO_PRIVILEGE;
+ String parentPath = Text.getRelativeParent(filter.getPath(), 1);
+
while (entries.hasNext()) {
ACLTemplate.Entry ace = (ACLTemplate.Entry) entries.next();
/*
- Determine if the ACE is defined on the node at absPath (locally):
- Except for READ-privileges the permissions must be determined
- from privileges defined for the parent. Consequently aces
- defined locally must be treated different than inherited entries.
+ Determine if the ACE also takes effect on the parent:
+ Some permissions (e.g. add-node or removal) must be determined
+ from privileges defined for the parent.
+ A 'local' entry defined on the target node never effects the
+ parent. For inherited ACEs determine if the ACE matches the
+ parent path.
*/
int entryBits = ace.getPrivilegeBits();
- boolean isLocal = existingNode && ace.isLocal(node.getNodeId());
- if (!isLocal) {
+ boolean isLocal = isExistingNode && ace.isLocal(node.getNodeId());
+ boolean matchesParent = (!isLocal && ace.matches(parentPath));
+ if (matchesParent) {
if (ace.isAllow()) {
parentAllows |= Permission.diff(entryBits, parentDenies);
} else {
@@ -516,7 +523,7 @@ public class ACLProvider extends Abstrac
}
boolean isAcItem = isAcItem(absPath);
- return buildResult(node, existingNode, isAcItem, new EntryFilterImpl(principalNames));
+ return buildResult(node, existingNode, isAcItem, new EntryFilterImpl(principalNames, absPath, session));
}
/**
@@ -545,32 +552,22 @@ public class ACLProvider extends Abstrac
*/
public boolean canRead(Path path, ItemId itemId) throws RepositoryException {
ItemId id = (itemId == null) ? session.getHierarchyManager().resolvePath(path) : itemId;
- /* currently READ access cannot be denied to individual properties.
- if the parent node is readable the properties are as well.
- this simplifies the canRead test as well as the caching.
- */
- boolean existingNode = false;
- NodeId nodeId;
- if (id.denotesNode()) {
- nodeId = (NodeId) id;
- // since method may only be called for existing nodes the
- // flag be set to true if the id identifies a node.
- existingNode = true;
- } else {
- nodeId = ((PropertyId) id).getParentId();
- }
-
+ // no extra check for existence as method may only be called for existing items.
+ boolean isExistingNode = id.denotesNode();
boolean canRead;
synchronized (monitor) {
- if (readCache.containsKey(nodeId)) {
- canRead = readCache.get(nodeId);
+ if (readCache.containsKey(id)) {
+ canRead = readCache.get(id);
} else {
ItemManager itemMgr = session.getItemManager();
+ NodeId nodeId = (isExistingNode) ? (NodeId) id : ((PropertyId) id).getParentId();
NodeImpl node = (NodeImpl) itemMgr.getItem(nodeId);
- Result result = buildResult(node, existingNode, isAcItem(node), new EntryFilterImpl(principalNames));
+ // TODO: check again if retrieving the path can be avoided
+ Path absPath = (path == null) ? session.getHierarchyManager().getPath(id) : path;
+ Result result = buildResult(node, isExistingNode, isAcItem(node), new EntryFilterImpl(principalNames, absPath, session));
canRead = result.grants(Permission.READ);
- readCache.put(nodeId, canRead);
+ readCache.put(id, canRead);
}
}
return canRead;
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java Tue Aug 10 09:58:03 2010
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
+import javax.jcr.NamespaceException;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
@@ -39,12 +40,14 @@ import org.apache.jackrabbit.core.Sessio
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
import org.apache.jackrabbit.core.security.authorization.AbstractACLTemplate;
-import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
+import org.apache.jackrabbit.core.security.authorization.GlobPattern;
import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
import org.apache.jackrabbit.core.security.principal.UnknownPrincipal;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,8 +65,7 @@ class ACLTemplate extends AbstractACLTem
private static final Logger log = LoggerFactory.getLogger(ACLTemplate.class);
/**
- * List containing the entries of this ACL Template with maximal one
- * grant and one deny ACE per principal.
+ * List containing the entries of this ACL Template.
*/
private final List<Entry> entries = new ArrayList<Entry>();
@@ -78,6 +80,11 @@ class ACLTemplate extends AbstractACLTem
private final PrivilegeRegistry privilegeRegistry;
/**
+ * The name resolver
+ */
+ private final NameResolver resolver;
+
+ /**
* The id of the access controlled node or <code>null</code> if this
* ACLTemplate isn't created for an existing access controlled node.
* Used for the Entry#isLocal(NodeId) call only in order to avoid calls
@@ -86,19 +93,30 @@ class ACLTemplate extends AbstractACLTem
private final NodeId id;
/**
+ *
+ */
+ private final String jcrRepGlob;
+
+ /**
* Construct a new empty {@link ACLTemplate}.
*
* @param path path
* @param privilegeRegistry registry
* @param valueFactory value factory
+ * @param resolver
* @param principalMgr manager
+ * @throws javax.jcr.NamespaceException
*/
ACLTemplate(String path, PrincipalManager principalMgr,
- PrivilegeRegistry privilegeRegistry, ValueFactory valueFactory) {
+ PrivilegeRegistry privilegeRegistry, ValueFactory valueFactory,
+ NamePathResolver resolver) throws NamespaceException {
super(path, valueFactory);
this.principalMgr = principalMgr;
this.privilegeRegistry = privilegeRegistry;
+ this.resolver = resolver;
this.id = null;
+
+ jcrRepGlob = resolver.getJCRName(P_GLOB);
}
/**
@@ -111,14 +129,16 @@ class ACLTemplate extends AbstractACLTem
*/
ACLTemplate(NodeImpl aclNode, PrivilegeRegistry privilegeRegistry) throws RepositoryException {
super((aclNode != null) ? aclNode.getParent().getPath() : null, (aclNode != null) ? aclNode.getSession().getValueFactory() : null);
- if (aclNode == null || !AccessControlConstants.NT_REP_ACL.equals(((NodeTypeImpl)aclNode.getPrimaryNodeType()).getQName())) {
+ if (aclNode == null || !NT_REP_ACL.equals(((NodeTypeImpl)aclNode.getPrimaryNodeType()).getQName())) {
throw new IllegalArgumentException("Node must be of type 'rep:ACL'");
}
SessionImpl sImpl = (SessionImpl) aclNode.getSession();
principalMgr = sImpl.getPrincipalManager();
this.privilegeRegistry = privilegeRegistry;
+ this.resolver = sImpl;
this.id = aclNode.getParentId();
+ jcrRepGlob = sImpl.getJCRName(P_GLOB);
// load the entries:
AccessControlManager acMgr = sImpl.getAccessControlManager();
@@ -126,23 +146,29 @@ class ACLTemplate extends AbstractACLTem
while (itr.hasNext()) {
NodeImpl aceNode = (NodeImpl) itr.nextNode();
try {
- String principalName = aceNode.getProperty(AccessControlConstants.P_PRINCIPAL_NAME).getString();
+ String principalName = aceNode.getProperty(P_PRINCIPAL_NAME).getString();
Principal princ = principalMgr.getPrincipal(principalName);
if (princ == null) {
log.debug("Principal with name " + principalName + " unknown to PrincipalManager.");
princ = new PrincipalImpl(principalName);
}
- Value[] privValues = aceNode.getProperty(AccessControlConstants.P_PRIVILEGES).getValues();
+ Value[] privValues = aceNode.getProperty(P_PRIVILEGES).getValues();
Privilege[] privs = new Privilege[privValues.length];
for (int i = 0; i < privValues.length; i++) {
privs[i] = acMgr.privilegeFromName(privValues[i].getString());
}
+
+ Map<String,Value> restrictions = null;
+ if (aceNode.hasProperty(P_GLOB)) {
+ restrictions = Collections.singletonMap(jcrRepGlob, aceNode.getProperty(P_GLOB).getValue());
+ }
// create a new ACEImpl (omitting validation check)
Entry ace = createEntry(
princ,
privs,
- AccessControlConstants.NT_REP_GRANT_ACE.equals(((NodeTypeImpl) aceNode.getPrimaryNodeType()).getQName()));
+ NT_REP_GRANT_ACE.equals(((NodeTypeImpl) aceNode.getPrimaryNodeType()).getQName()),
+ restrictions);
// add the entry
internalAdd(ace);
} catch (RepositoryException e) {
@@ -157,10 +183,15 @@ class ACLTemplate extends AbstractACLTem
* @param principal
* @param privileges
* @param isAllow
- * @return
+ * @param restrictions
+ * @return A new entry
*/
- Entry createEntry(Principal principal, Privilege[] privileges, boolean isAllow) throws AccessControlException {
- return new Entry(principal, privileges, isAllow);
+ Entry createEntry(Principal principal, Privilege[] privileges, boolean isAllow, Map<String,Value> restrictions) throws RepositoryException {
+ return new Entry(principal, privileges, isAllow, restrictions);
+ }
+
+ Entry createEntry(Entry base, Privilege[] newPrivileges, boolean isAllow) throws RepositoryException {
+ return new Entry(base, newPrivileges, isAllow);
}
private List<Entry> internalGetEntries(Principal principal) {
@@ -174,7 +205,7 @@ class ACLTemplate extends AbstractACLTem
return entriesPerPrincipal;
}
- private synchronized boolean internalAdd(Entry entry) throws AccessControlException {
+ private synchronized boolean internalAdd(Entry entry) throws RepositoryException {
Principal principal = entry.getPrincipal();
List<Entry> entriesPerPrincipal = internalGetEntries(principal);
if (entriesPerPrincipal.isEmpty()) {
@@ -191,26 +222,29 @@ class ACLTemplate extends AbstractACLTem
Entry complementEntry = null;
for (Entry e : entriesPerPrincipal) {
- if (entry.isAllow() == e.isAllow()) {
- int existingPrivs = e.getPrivilegeBits();
- if ((existingPrivs | ~entry.getPrivilegeBits()) == -1) {
- // all privileges to be granted/denied are already present
- // in the existing entry -> not modified
- return false;
+ if (equalRestriction(entry, e)) {
+ if (entry.isAllow() == e.isAllow()) {
+ // need to update an existing entry
+ int existingPrivs = e.getPrivilegeBits();
+ if ((existingPrivs | ~entry.getPrivilegeBits()) == -1) {
+ // all privileges to be granted/denied are already present
+ // in the existing entry -> not modified
+ return false;
+ }
+
+ // remember the index of the existing entry to be updated later on.
+ updateIndex = entries.indexOf(e);
+
+ // remove the existing entry and create a new that includes
+ // both the new privileges and the existing ones.
+ entries.remove(e);
+ int mergedBits = e.getPrivilegeBits() | entry.getPrivilegeBits();
+ Privilege[] mergedPrivs = privilegeRegistry.getPrivileges(mergedBits);
+ // omit validation check.
+ entry = createEntry(entry, mergedPrivs, entry.isAllow());
+ } else {
+ complementEntry = e;
}
-
- // remember the index of the existing entry to be updated later on.
- updateIndex = entries.indexOf(e);
-
- // remove the existing entry and create a new that includes
- // both the new privileges and the existing ones.
- entries.remove(e);
- int mergedBits = e.getPrivilegeBits() | entry.getPrivilegeBits();
- Privilege[] mergedPrivs = privilegeRegistry.getPrivileges(mergedBits);
- // omit validation check.
- entry = createEntry(entry.getPrincipal(), mergedPrivs, entry.isAllow());
- } else {
- complementEntry = e;
}
}
@@ -232,7 +266,7 @@ class ACLTemplate extends AbstractACLTem
// replace the existing entry having the privileges adjusted
int index = entries.indexOf(complementEntry);
entries.remove(complementEntry);
- Entry tmpl = createEntry(entry.getPrincipal(),
+ Entry tmpl = createEntry(entry,
privilegeRegistry.getPrivileges(resultPrivs),
!entry.isAllow());
entries.add(index, tmpl);
@@ -250,6 +284,13 @@ class ACLTemplate extends AbstractACLTem
}
}
+ private boolean equalRestriction(Entry entry1, Entry entry2) throws RepositoryException {
+ Value v1 = entry1.getRestriction(jcrRepGlob);
+ Value v2 = entry2.getRestriction(jcrRepGlob);
+
+ return (v1 == null) ? v2 == null : v1.equals(v2);
+ }
+
//------------------------------------------------< AbstractACLTemplate >---
/**
* @see AbstractACLTemplate#checkValidEntry(java.security.Principal, javax.jcr.security.Privilege[], boolean, java.util.Map)
@@ -258,9 +299,6 @@ class ACLTemplate extends AbstractACLTem
protected void checkValidEntry(Principal principal, Privilege[] privileges,
boolean isAllow, Map<String, Value> restrictions)
throws AccessControlException {
- if (restrictions != null && !restrictions.isEmpty()) {
- throw new AccessControlException("This AccessControlList does not allow for additional restrictions.");
- }
// validate principal
if (principal instanceof UnknownPrincipal) {
log.debug("Consider fallback principal as valid: {}", principal.getName());
@@ -295,32 +333,36 @@ class ACLTemplate extends AbstractACLTem
//----------------------------------------< JackrabbitAccessControlList >---
/**
- * Returns an empty String array.
- *
- * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#getRestrictionType(String)
+ * @see JackrabbitAccessControlList#getRestrictionNames()
*/
public String[] getRestrictionNames() {
- return new String[0];
+ return new String[] {jcrRepGlob};
}
/**
- * Always returns {@link PropertyType#UNDEFINED} as no restrictions are
- * supported.
- *
* @see JackrabbitAccessControlList#getRestrictionType(String)
*/
public int getRestrictionType(String restrictionName) {
- return PropertyType.UNDEFINED;
+ if (jcrRepGlob.equals(restrictionName) || P_GLOB.toString().equals(restrictionName)) {
+ return PropertyType.STRING;
+ } else {
+ return PropertyType.UNDEFINED;
+ }
}
/**
+ * The only known restriction is:
+ * <pre>
+ * rep:glob (optional) value-type: STRING
+ * </pre>
+ *
* @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean, Map)
*/
public boolean addEntry(Principal principal, Privilege[] privileges,
boolean isAllow, Map<String, Value> restrictions)
throws AccessControlException, RepositoryException {
checkValidEntry(principal, privileges, isAllow, restrictions);
- Entry ace = createEntry(principal, privileges, isAllow);
+ Entry ace = createEntry(principal, privileges, isAllow, restrictions);
return internalAdd(ace);
}
@@ -363,11 +405,29 @@ class ACLTemplate extends AbstractACLTem
*/
class Entry extends AccessControlEntryImpl {
- private Entry(Principal principal, Privilege[] privileges, boolean allow)
- throws AccessControlException {
- super(principal, privileges, allow, Collections.<String, Value>emptyMap(), valueFactory);
+ private final GlobPattern pattern;
+
+ private Entry(Principal principal, Privilege[] privileges, boolean allow, Map<String,Value> restrictions)
+ throws RepositoryException {
+ super(principal, privileges, allow, restrictions);
+ Value glob = getRestrictions().get(P_GLOB);
+ if (glob != null) {
+ pattern = GlobPattern.create(path, glob.getString());
+ } else {
+ pattern = GlobPattern.create(path);
+ }
}
+ private Entry(Entry base, Privilege[] newPrivileges, boolean isAllow) throws RepositoryException {
+ super(base, newPrivileges, isAllow);
+ Value glob = getRestrictions().get(P_GLOB);
+ if (glob != null) {
+ pattern = GlobPattern.create(path, glob.getString());
+ } else {
+ pattern = GlobPattern.create(path);
+ }
+ }
+
/**
* @param nodeId
* @return <code>true</code> if this entry is defined on the node
@@ -376,5 +436,24 @@ class ACLTemplate extends AbstractACLTem
boolean isLocal(NodeId nodeId) {
return id != null && id.equals(nodeId);
}
+
+ /**
+ *
+ * @param jcrPath
+ * @return
+ */
+ boolean matches(String jcrPath) {
+ return pattern.matches(jcrPath);
+ }
+
+ @Override
+ protected NameResolver getResolver() {
+ return resolver;
+ }
+
+ @Override
+ protected ValueFactory getValueFactory() {
+ return valueFactory;
+ }
}
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java Tue Aug 10 09:58:03 2010
@@ -16,9 +16,12 @@
*/
package org.apache.jackrabbit.core.security.authorization.acl;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.jcr.NamespaceException;
import javax.jcr.security.AccessControlEntry;
import java.security.acl.Group;
import java.util.Collection;
@@ -35,9 +38,15 @@ class EntryFilterImpl implements EntryFi
private static final Logger log = LoggerFactory.getLogger(EntryFilterImpl.class);
private final Collection<String> principalNames;
+ private final Path path;
+ private final PathResolver resolver;
- EntryFilterImpl(Collection<String> principalNames) {
+ private String itemPath;
+
+ EntryFilterImpl(Collection<String> principalNames, Path path, PathResolver resolver) {
this.principalNames = principalNames;
+ this.path = path;
+ this.resolver = resolver;
}
/**
@@ -59,7 +68,7 @@ class EntryFilterImpl implements EntryFi
// first collect aces present on the given aclNode.
for (AccessControlEntry ace : entries) {
// only process ace if 'principalName' is contained in the given set
- if (principalNames == null || principalNames.contains(ace.getPrincipal().getName())) {
+ if (matches(ace)) {
// add it to the proper list (e.g. separated by principals)
/**
* NOTE: access control entries must be collected in reverse
@@ -76,4 +85,32 @@ class EntryFilterImpl implements EntryFi
log.warn("Filtering aborted. Expected 2 result lists.");
}
}
+
+ private boolean matches(AccessControlEntry ace) {
+ if (principalNames == null || principalNames.contains(ace.getPrincipal().getName())) {
+ if (((ACLTemplate.Entry) ace).getRestrictions().isEmpty()) {
+ // short cut: there is no glob-restriction -> the entry matches
+ // because it is either defined on the node or inherited.
+ return true;
+ } else {
+ // there is a glob-restriction: check if the target path matches
+ // this entry.
+ try {
+ return ((ACLTemplate.Entry) ace).matches(getPath());
+ } catch (NamespaceException e) {
+ log.error("Cannot determine ACE match.", e);
+ }
+ }
+ }
+
+ // doesn't match this filter -> ignore
+ return false;
+ }
+
+ String getPath() throws NamespaceException {
+ if (itemPath == null) {
+ itemPath = resolver.getJCRPath(path);
+ }
+ return itemPath;
+ }
}
\ No newline at end of file
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLEditor.java Tue Aug 10 09:58:03 2010
@@ -21,7 +21,6 @@ import javax.jcr.security.AccessControlE
import javax.jcr.security.Privilege;
import javax.jcr.security.AccessControlPolicy;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.core.NodeImpl;
@@ -29,6 +28,7 @@ import org.apache.jackrabbit.core.Protec
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
+import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
@@ -48,6 +48,7 @@ import javax.jcr.ValueFactory;
import javax.jcr.PropertyType;
import javax.jcr.NodeIterator;
import java.security.Principal;
+import java.util.Set;
/**
* <code>ACLEditor</code>...
@@ -206,7 +207,7 @@ public class ACLEditor extends Protected
/* add all new entries defined on the template */
AccessControlEntry[] aces = acl.getAccessControlEntries();
for (AccessControlEntry ace1 : aces) {
- JackrabbitAccessControlEntry ace = (JackrabbitAccessControlEntry) ace1;
+ AccessControlEntryImpl ace = (AccessControlEntryImpl) ace1;
// create the ACE node
Name nodeName = getUniqueNodeName(aclNode, "entry");
@@ -225,11 +226,10 @@ public class ACLEditor extends Protected
setProperty(aceNode, P_PRIVILEGES, vs);
// store the restrictions:
- String[] restrNames = ace.getRestrictionNames();
- for (String restrName : restrNames) {
- Name pName = session.getQName(restrName);
+ Set<Name> restrNames = ace.getRestrictions().keySet();
+ for (Name restrName : restrNames) {
Value value = ace.getRestriction(restrName);
- setProperty(aceNode, pName, value);
+ setProperty(aceNode, restrName, value);
}
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java Tue Aug 10 09:58:03 2010
@@ -109,7 +109,6 @@ public class ACLProvider extends Abstrac
ValueFactory vf = session.getValueFactory();
Map<String, Value> restrictions = new HashMap<String, Value>();
restrictions.put(session.getJCRName(ACLTemplate.P_NODE_PATH), vf.createValue(root.getPath(), PropertyType.PATH));
- restrictions.put(session.getJCRName(ACLTemplate.P_GLOB), vf.createValue(GlobPattern.WILDCARD_ALL));
PrincipalManager pMgr = session.getPrincipalManager();
AccessControlManager acMgr = session.getAccessControlManager();
@@ -307,7 +306,8 @@ public class ACLProvider extends Abstrac
private boolean canReadAll;
- private final Map<ItemId, Boolean> readCache = new LRUMap(2000);
+ // TODO find optimal cache size and ev. make it configurable (see also JCR-2573).
+ private final Map<ItemId, Boolean> readCache = new LRUMap(5000);
private final Object monitor = new Object();
/**
@@ -351,8 +351,7 @@ public class ACLProvider extends Abstrac
// and retrieve the entries from the entry-collector.
entries = entriesCache.getEntries(principals);
- // in addition: trivial check if read access is deny somewhere
- // as as shortcut in #canRead(Path)
+ // in addition: trivial check if read access is denied somewhere
canReadAll = canRead(session.getQPath("/"));
if (canReadAll) {
for (AccessControlEntry entry : entries) {
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java Tue Aug 10 09:58:03 2010
@@ -21,8 +21,10 @@ import org.apache.jackrabbit.core.NodeIm
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
import org.apache.jackrabbit.core.security.authorization.AbstractACLTemplate;
+import org.apache.jackrabbit.core.security.authorization.GlobPattern;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,12 +63,6 @@ class ACLTemplate extends AbstractACLTem
* node itself).
*/
static final Name P_NODE_PATH = NF.create(Name.NS_REP_URI, "nodePath");
- /**
- * rep:glob property name used to restrict the number of child nodes
- * or properties that are affected by the privileges applied at
- * rep:nodePath
- */
- static final Name P_GLOB = NF.create(Name.NS_REP_URI, "glob");
private final Principal principal;
@@ -75,6 +71,8 @@ class ACLTemplate extends AbstractACLTem
private final String jcrNodePathName;
private final String jcrGlobName;
+ private final NameResolver resolver;
+
ACLTemplate(Principal principal, String path, NamePathResolver resolver, ValueFactory vf)
throws RepositoryException {
this(principal, path, null, resolver, vf);
@@ -95,6 +93,8 @@ class ACLTemplate extends AbstractACLTem
jcrNodePathName = resolver.getJCRName(P_NODE_PATH);
jcrGlobName = resolver.getJCRName(P_GLOB);
+ this.resolver = resolver;
+
if (acNode != null && acNode.hasNode(N_POLICY)) {
// build the list of policy entries;
NodeImpl aclNode = acNode.getNode(N_POLICY);
@@ -132,23 +132,34 @@ class ACLTemplate extends AbstractACLTem
}
AccessControlEntry createEntry(Principal princ, Privilege[] privileges,
- boolean allow, Map<String, Value> restrictions)
- throws RepositoryException {
- checkValidEntry(princ, privileges, allow, restrictions);
+ boolean allow, Map<String, Value> restrictions) throws RepositoryException {
+ // adjust restrictions if necessary
+ Map<String, Value> rest = adjustRestrictions(restrictions);
+ checkValidEntry(princ, privileges, allow, rest);
+ return new Entry(princ, privileges, allow, rest);
+ }
+ private Map<String, Value> adjustRestrictions(Map<String, Value> restrictions) throws RepositoryException {
// make sure the nodePath restriction is of type PATH
Value v = restrictions.get(jcrNodePathName);
- if (v.getType() != PropertyType.PATH) {
+ if (v == null) {
+ v = restrictions.get(P_NODE_PATH.toString());
+ }
+ if (v != null && v.getType() != PropertyType.PATH) {
v = valueFactory.createValue(v.getString(), PropertyType.PATH);
restrictions.put(jcrNodePathName, v);
}
// ... and glob is of type STRING.
v = restrictions.get(jcrGlobName);
+ if (v == null) {
+ v = restrictions.get(P_GLOB.toString());
+ }
if (v != null && v.getType() != PropertyType.STRING) {
v = valueFactory.createValue(v.getString(), PropertyType.STRING);
restrictions.put(jcrGlobName, v);
}
- return new Entry(princ, privileges, allow, restrictions);
+
+ return restrictions;
}
//------------------------------------------------< AbstractACLTemplate >---
@@ -164,7 +175,7 @@ class ACLTemplate extends AbstractACLTem
}
Set<String> rNames = restrictions.keySet();
- if (!rNames.contains(jcrNodePathName)) {
+ if (!rNames.contains(jcrNodePathName) && !rNames.contains(P_NODE_PATH.toString())) {
throw new AccessControlException("Missing mandatory restriction: " + jcrNodePathName);
}
}
@@ -189,9 +200,9 @@ class ACLTemplate extends AbstractACLTem
* @see JackrabbitAccessControlList#getRestrictionType(String)
*/
public int getRestrictionType(String restrictionName) {
- if (jcrNodePathName.equals(restrictionName)) {
+ if (jcrNodePathName.equals(restrictionName) || P_NODE_PATH.toString().equals(restrictionName)) {
return PropertyType.PATH;
- } else if (jcrGlobName.equals(restrictionName)) {
+ } else if (jcrGlobName.equals(restrictionName) || P_GLOB.toString().equals(restrictionName)) {
return PropertyType.STRING;
} else {
return PropertyType.UNDEFINED;
@@ -294,16 +305,13 @@ class ACLTemplate extends AbstractACLTem
private Entry(Principal principal, Privilege[] privileges, boolean allow,
Map<String, Value> restrictions)
throws AccessControlException, RepositoryException {
- super(principal, privileges, allow, restrictions, valueFactory);
+ super(principal, privileges, allow, restrictions);
- // TODO: review again
- Value np = getRestriction(jcrNodePathName);
- nodePath = getRestriction(jcrNodePathName).getString();
- Value glob = getRestriction(jcrGlobName);
+ Map<Name, Value> rstr = getRestrictions();
+ nodePath = rstr.get(P_NODE_PATH).getString();
+ Value glob = rstr.get(P_GLOB);
if (glob != null) {
- StringBuffer b = new StringBuffer(nodePath);
- b.append(glob.getString());
- pattern = GlobPattern.create(b.toString());
+ pattern = GlobPattern.create(nodePath, glob.getString());
} else {
pattern = GlobPattern.create(nodePath);
}
@@ -320,5 +328,15 @@ class ACLTemplate extends AbstractACLTem
boolean matchesNodePath(String jcrPath) {
return nodePath.equals(jcrPath);
}
+
+ @Override
+ protected NameResolver getResolver() {
+ return resolver;
+ }
+
+ @Override
+ protected ValueFactory getValueFactory() {
+ return valueFactory;
+ }
}
}
\ No newline at end of file
Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractWriteTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractWriteTest.java?rev=983906&r1=983905&r2=983906&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractWriteTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractWriteTest.java Tue Aug 10 09:58:03 2010
@@ -32,6 +32,8 @@ import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.observation.Event;
import javax.jcr.observation.ObservationManager;
@@ -39,9 +41,11 @@ import javax.jcr.security.AccessControlM
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.Privilege;
import java.security.Principal;
+import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Map;
import java.util.UUID;
/**
@@ -1065,6 +1069,207 @@ public abstract class AbstractWriteTest
testSession.save();
}
+ /**
+ * Test the rep:glob restriction
+ *
+ * @throws Exception
+ */
+ public void testGlobRestriction() throws Exception {
+ Session testSession = getTestSession();
+ AccessControlManager testAcMgr = getTestACManager();
+ ValueFactory vf = superuser.getValueFactory();
+ /*
+ precondition:
+ testuser must have READ-only permission on test-node and below
+ */
+ checkReadOnly(path);
+ checkReadOnly(childNPath);
+
+ Node child = superuser.getNode(childNPath).addNode(nodeName3);
+ superuser.save();
+ String childchildPath = child.getPath();
+
+ Privilege[] write = privilegesFromName(PrivilegeRegistry.REP_WRITE);
+ String writeActions = Session.ACTION_ADD_NODE +","+Session.ACTION_REMOVE +","+ Session.ACTION_SET_PROPERTY;
+
+
+ // permissions defined @ path
+ // restriction: grants write priv to all nodeName3 children
+ Map<String, Value> restrictions = new HashMap(getRestrictions(superuser, path));
+ restrictions.put(AccessControlConstants.P_GLOB.toString(), vf.createValue("/*"+nodeName3));
+ givePrivileges(path, write, restrictions);
+
+ assertFalse(testAcMgr.hasPrivileges(path, write));
+ assertFalse(testSession.hasPermission(path, javax.jcr.Session.ACTION_SET_PROPERTY));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath, write));
+ assertFalse(testSession.hasPermission(childNPath, javax.jcr.Session.ACTION_SET_PROPERTY));
+
+ assertTrue(testAcMgr.hasPrivileges(childNPath2, write));
+ assertTrue(testSession.hasPermission(childNPath2, Session.ACTION_SET_PROPERTY));
+ assertFalse(testSession.hasPermission(childNPath2, writeActions)); // removal req. rmchildnode privilege on parent.
+
+ assertTrue(testAcMgr.hasPrivileges(childchildPath, write));
+ }
+
+ /**
+ * Test the rep:glob restriction
+ *
+ * @throws Exception
+ */
+ public void testGlobRestriction2() throws Exception {
+ Session testSession = getTestSession();
+ AccessControlManager testAcMgr = getTestACManager();
+ ValueFactory vf = superuser.getValueFactory();
+ /*
+ precondition:
+ testuser must have READ-only permission on test-node and below
+ */
+ checkReadOnly(path);
+ checkReadOnly(childNPath);
+
+ Node child = superuser.getNode(childNPath).addNode(nodeName3);
+ superuser.save();
+ String childchildPath = child.getPath();
+
+ Privilege[] write = privilegesFromName(PrivilegeRegistry.REP_WRITE);
+ Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES);
+ Privilege[] rmNode = privilegesFromName(Privilege.JCR_REMOVE_NODE);
+
+ Map<String, Value> restrictions = new HashMap(getRestrictions(superuser, path));
+
+ // permissions defined @ path
+ // restriction: grants write-priv to nodeName3 grand-children but not direct nodeName3 children.
+ restrictions.put(AccessControlConstants.P_GLOB.toString(), vf.createValue("/*/"+nodeName3));
+ givePrivileges(path, write, restrictions);
+
+ assertFalse(testAcMgr.hasPrivileges(path, write));
+ assertFalse(testAcMgr.hasPrivileges(path, rmNode));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath, addNode));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath2, write));
+
+ assertTrue(testAcMgr.hasPrivileges(childchildPath, write));
+ }
+
+ /**
+ * Test the rep:glob restriction
+ *
+ * @throws Exception
+ */
+ public void testGlobRestriction3() throws Exception {
+ Session testSession = getTestSession();
+ AccessControlManager testAcMgr = getTestACManager();
+ ValueFactory vf = superuser.getValueFactory();
+ /*
+ precondition:
+ testuser must have READ-only permission on test-node and below
+ */
+ checkReadOnly(path);
+ checkReadOnly(childNPath);
+
+ Node child = superuser.getNode(childNPath).addNode(nodeName3);
+ superuser.save();
+ String childchildPath = child.getPath();
+
+ Privilege[] write = privilegesFromName(PrivilegeRegistry.REP_WRITE);
+ Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES);
+ String writeActions = Session.ACTION_ADD_NODE +","+Session.ACTION_REMOVE +","+ Session.ACTION_SET_PROPERTY;
+
+ Map<String, Value> restrictions = new HashMap(getRestrictions(superuser, path));
+
+ // permissions defined @ path
+ // restriction: allows write to nodeName3 children
+ restrictions.put(AccessControlConstants.P_GLOB.toString(), vf.createValue("/*/"+nodeName3));
+ givePrivileges(path, write, restrictions);
+ // and grant add-node only at path (no glob restriction)
+ givePrivileges(path, addNode, getRestrictions(superuser, path));
+
+ assertFalse(testAcMgr.hasPrivileges(path, write));
+ assertTrue(testAcMgr.hasPrivileges(path, addNode));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath, write));
+ assertTrue(testAcMgr.hasPrivileges(childNPath, addNode));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath2, write));
+ assertTrue(testAcMgr.hasPrivileges(childchildPath, write));
+ }
+
+ /**
+ * Test the rep:glob restriction
+ *
+ * @throws Exception
+ */
+ public void testGlobRestriction4() throws Exception {
+ Session testSession = getTestSession();
+ AccessControlManager testAcMgr = getTestACManager();
+ ValueFactory vf = superuser.getValueFactory();
+ /*
+ precondition:
+ testuser must have READ-only permission on test-node and below
+ */
+ checkReadOnly(path);
+ checkReadOnly(childNPath);
+
+ Node child = superuser.getNode(childNPath).addNode(nodeName3);
+ superuser.save();
+ String childchildPath = child.getPath();
+
+ Privilege[] write = privilegesFromName(PrivilegeRegistry.REP_WRITE);
+ Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES);
+
+ Map<String, Value> restrictions = new HashMap(getRestrictions(superuser, path));
+ restrictions.put(AccessControlConstants.P_GLOB.toString(), vf.createValue("/*"+nodeName3));
+ givePrivileges(path, write, restrictions);
+
+ withdrawPrivileges(childNPath2, addNode, getRestrictions(superuser, childNPath2));
+
+ assertFalse(testAcMgr.hasPrivileges(path, write));
+ assertFalse(testSession.hasPermission(path, javax.jcr.Session.ACTION_REMOVE));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath, write));
+ assertFalse(testSession.hasPermission(childNPath, javax.jcr.Session.ACTION_REMOVE));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath2, write));
+
+ assertTrue(testAcMgr.hasPrivileges(childchildPath, write));
+ }
+
+
+ /**
+ * Test the rep:glob restriction
+ *
+ * @throws Exception
+ */
+ public void testCancelInheritanceRestriction() throws Exception {
+ Session testSession = getTestSession();
+ AccessControlManager testAcMgr = getTestACManager();
+ ValueFactory vf = superuser.getValueFactory();
+ /*
+ precondition:
+ testuser must have READ-only permission on test-node and below
+ */
+ checkReadOnly(path);
+ checkReadOnly(childNPath);
+
+ Privilege[] write = privilegesFromName(PrivilegeRegistry.REP_WRITE);
+ Privilege[] addNode = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES);
+
+ Map<String, Value> restrictions = new HashMap(getRestrictions(superuser, path));
+ restrictions.put(AccessControlConstants.P_GLOB.toString(), vf.createValue(""));
+ givePrivileges(path, write, restrictions);
+
+ assertTrue(testAcMgr.hasPrivileges(path, write));
+ assertTrue(testSession.hasPermission(path, Session.ACTION_SET_PROPERTY));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath, write));
+ assertFalse(testSession.hasPermission(childNPath, Session.ACTION_SET_PROPERTY));
+
+ assertFalse(testAcMgr.hasPrivileges(childNPath2, write));
+ assertFalse(testSession.hasPermission(childNPath2, Session.ACTION_SET_PROPERTY));
+ }
+
private static Node findPolicyNode(Node start) throws RepositoryException {
Node policyNode = null;
if (start.isNodeType("rep:Policy")) {
|