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 * null 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 restrictions; - - /** - * Value factory - */ - private final ValueFactory valueFactory; + private final Map 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 restrictions, - ValueFactory valueFactory) - throws AccessControlException { + boolean isAllow, Map 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(restrictions.size()); + this.restrictions = new HashMap(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(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 getRestrictions() { + return Collections.unmodifiableMap(restrictions); + } + + /** + * @param restrictionName + * @return The restriction with the specified name or null. + */ + 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; /** - * GlobPattern... + * GlobPattern defines a simplistic pattern matching. It consists + * of a mandatory (leading) path and an optional "glob" that may contain one or + * more wildcard characters ("*") according to the glob matching + * defined by {@link javax.jcr.Node#getNodes(String[])}. In contrast to that + * method the GlobPattern operates on path (not only names). + *

+ * + *

+ * Please note the following special cases: + *

+ * NodePath     |   Restriction   |   Matches
+ * -----------------------------------------------------------------------------
+ * /foo         |   null          |   matches /foo and all children of /foo
+ * /foo         |   ""            |   matches /foo only
+ * 
+ *

+ * + *

+ * Examples including wildcard char: + *

+ * 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'
+ * 
+ *

*/ -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 true if matches, false 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; /** * ACLEditor... @@ -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 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 principalNames; - private final Map readCache = new LRUMap(1000); + // TODO find optimal cache size and ev. make it configurable (see also JCR-2573). + private final Map readCache = new LRUMap(5000); private final Object monitor = new Object(); private AclPermissions(Set 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 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 entries = new ArrayList(); @@ -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 null 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 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 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 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 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 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: + *
+     *   rep:glob (optional)  value-type: STRING
+     * 
+ * * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean, Map) */ public boolean addEntry(Principal principal, Privilege[] privileges, boolean isAllow, Map 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.emptyMap(), valueFactory); + private final GlobPattern pattern; + + private Entry(Principal principal, Privilege[] privileges, boolean allow, Map 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 true 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 principalNames; + private final Path path; + private final PathResolver resolver; - EntryFilterImpl(Collection principalNames) { + private String itemPath; + + EntryFilterImpl(Collection 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; /** * ACLEditor... @@ -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 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 restrictions = new HashMap(); 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 readCache = new LRUMap(2000); + // TODO find optimal cache size and ev. make it configurable (see also JCR-2573). + private final Map 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 restrictions) - throws RepositoryException { - checkValidEntry(princ, privileges, allow, restrictions); + boolean allow, Map restrictions) throws RepositoryException { + // adjust restrictions if necessary + Map rest = adjustRestrictions(restrictions); + checkValidEntry(princ, privileges, allow, rest); + return new Entry(princ, privileges, allow, rest); + } + private Map adjustRestrictions(Map 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 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 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 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 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 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 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 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 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")) {