You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2016/10/11 20:08:44 UTC
svn commit: r1764337 - in /subversion/branches/authzperf/subversion:
libsvn_repos/authz.c tests/libsvn_repos/repos-test.c
Author: stefan2
Date: Tue Oct 11 20:08:44 2016
New Revision: 1764337
URL: http://svn.apache.org/viewvc?rev=1764337&view=rev
Log:
On the authzperf branch:
Make handling of "**" (any-var) match user's expectations. See test cases.
This fixes the calculation of recursive access rights. The pre-calculated
information on the tree nodes must only cover the bottom-up view, i.e. what
range of access rights has been defined anywhere in the sub-tree. Top-down
application of inheritance must be restricted to the lookup - which already
fully handles it. Hence, we only need finalize_tree instead of
finalize_up_tree and finalize_down_tree.
The second change in here is that trailing any-var patterns will actually
eclipse (hide / replace) any previous rule for the respective sub-tree.
That is relevant for recursive right checks which don't operate on the
repository node tree but only on the set of *potentially* applicable
rules for that sub-tree. This implements the only easy-to-handle special
case of a pattern replacing a set of other pattern rules.
* subversion/libsvn_repos/authz.c
(limited_rights_t): Commentary reflects that these bottom-up only now.
(trim_subnode_hash,
trim_subnode_array,
trim_tree): New functions implementing the any-var eclipses older rule
principle.
(finalize_up_subnode_array,
finalize_up_tree): Rename to ...
(finalize_subnode_array,
finalize_tree): ... this. No longer take the parent rights into account.
(finalize_subnode_hash): New utility function factored out from
finalize_tree.
(finalize_up_subnode_array,
finalize_up_tree): Remove.
(create_user_authz): Update caller. Invoke the new trim function, too.
* subversion/tests/libsvn_repos/repos-test.c
(test_authz_recursive_override,
test_authz_pattern_tests): New test cases.
(test_funcs): Register new tests.
Modified:
subversion/branches/authzperf/subversion/libsvn_repos/authz.c
subversion/branches/authzperf/subversion/tests/libsvn_repos/repos-test.c
Modified: subversion/branches/authzperf/subversion/libsvn_repos/authz.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/authz.c?rev=1764337&r1=1764336&r2=1764337&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/authz.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/authz.c Tue Oct 11 20:08:44 2016
@@ -80,11 +80,11 @@ typedef struct limited_rights_t
access_t access;
/* Minimal access rights that the user has on this or any other node in
- * the sub-tree. */
+ * the sub-tree. This does not take inherited rights into account. */
authz_access_t min_rights;
/* Maximal access rights that the user has on this or any other node in
- * the sub-tree. */
+ * the sub-tree. This does not take inherited rights into account. */
authz_access_t max_rights;
} limited_rights_t;
@@ -484,28 +484,207 @@ process_acl(construction_context_t *ctx,
}
/* Forward declaration ... */
+static svn_boolean_t
+trim_tree(node_t *node,
+ int latest_any_var,
+ apr_pool_t *scratch_pool);
+
+/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *HASH of
+ * node_t * and remove empty nodes from. *HASH may be NULL. If all nodes
+ * could be removed, set *HASH to NULL and return TRUE. Allocate temporary
+ * data in SCRATCH_POOL.
+ */
+static svn_boolean_t
+trim_subnode_hash(apr_hash_t **hash,
+ int latest_any_var,
+ apr_pool_t *scratch_pool)
+{
+ if (*hash)
+ {
+ apr_array_header_t *to_remove = apr_array_make(scratch_pool, 0,
+ sizeof(node_t *));
+
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(scratch_pool, *hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ node_t *node = apr_hash_this_val(hi);
+ if (trim_tree(node, latest_any_var, scratch_pool))
+ APR_ARRAY_PUSH(to_remove, node_t *) = node;
+ }
+
+ /* Are some nodes left? */
+ if (to_remove->nelts < apr_hash_count(*hash))
+ {
+ /* Remove empty nodes (if any). */
+ int i;
+ for (i = 0; i < to_remove->nelts; ++i)
+ {
+ node_t *node = APR_ARRAY_IDX(to_remove, i, node_t *);
+ apr_hash_set(*hash, node->segment.data, node->segment.len,
+ NULL);
+ }
+
+ return FALSE;
+ }
+
+ /* No nodes left. A NULL hash is more efficient than an empty one. */
+ *hash = NULL;
+ }
+
+ return TRUE;
+}
+
+/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *ARRAY of
+ * node_t * and remove empty nodes from. *ARRAY may be NULL. If all nodes
+ * could be removed, set *ARRAY to NULL and return TRUE. Allocate
+ * temporary data in SCRATCH_POOL.
+ */
+static svn_boolean_t
+trim_subnode_array(apr_array_header_t **array,
+ int latest_any_var,
+ apr_pool_t *scratch_pool)
+{
+ if (*array)
+ {
+ int i, dest;
+ for (i = 0, dest = 0; i < (*array)->nelts; ++i)
+ {
+ node_t *node = APR_ARRAY_IDX(*array, i, sorted_pattern_t).node;
+ if (!trim_tree(node, latest_any_var, scratch_pool))
+ {
+ if (i != dest)
+ APR_ARRAY_IDX(*array, dest, sorted_pattern_t)
+ = APR_ARRAY_IDX(*array, i, sorted_pattern_t);
+ ++dest;
+ }
+ }
+
+ /* Are some nodes left? */
+ if (dest)
+ {
+ /* Trim it to the number of valid entries. */
+ (*array)->nelts = dest;
+ return FALSE;
+ }
+
+ /* No nodes left. A NULL array is more efficient than an empty one. */
+ *array = NULL;
+ }
+
+ return TRUE;
+}
+
+/* Remove all rules and sub-nodes from NODE that are fully eclipsed by the
+ * "any-var" rule with sequence number LATEST_ANY_VAR. Return TRUE, if
+ * there are no rules left in the sub-tree, including NODE.
+ * Allocate temporary data in SCRATCH_POOL.
+ */
+static svn_boolean_t
+trim_tree(node_t *node,
+ int latest_any_var,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t removed_all = TRUE;
+
+ /* For convenience, we allow NODE to be NULL: */
+ if (!node)
+ return TRUE;
+
+ /* Do we have a later "any_var" rule at this node. */
+ if ( node->pattern_sub_nodes
+ && node->pattern_sub_nodes->any_var
+ && node->pattern_sub_nodes->any_var->rights.access.sequence_number
+ > latest_any_var)
+ {
+ latest_any_var
+ = node->pattern_sub_nodes->any_var->rights.access.sequence_number;
+ }
+
+ /* Is there a local rule at this node that is not eclipsed by any_var? */
+ if (has_local_rule(&node->rights))
+ {
+ /* Remove the local rule, if it got eclipsed.
+ * Note that for the latest any_var node, the sequence number is equal. */
+ if (node->rights.access.sequence_number >= latest_any_var)
+ removed_all = FALSE;
+ else
+ node->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
+ }
+
+ /* Process all sub-nodes. */
+ removed_all &= trim_subnode_hash(&node->sub_nodes, latest_any_var,
+ scratch_pool);
+
+ if (node->pattern_sub_nodes)
+ {
+ if (trim_tree(node->pattern_sub_nodes->any, latest_any_var,
+ scratch_pool))
+ node->pattern_sub_nodes->any = NULL;
+ else
+ removed_all = FALSE;
+
+ if (trim_tree(node->pattern_sub_nodes->any_var, latest_any_var,
+ scratch_pool))
+ node->pattern_sub_nodes->any_var = NULL;
+ else
+ removed_all = FALSE;
+
+ removed_all &= trim_subnode_array(&node->pattern_sub_nodes->prefixes,
+ latest_any_var, scratch_pool);
+ removed_all &= trim_subnode_array(&node->pattern_sub_nodes->suffixes,
+ latest_any_var, scratch_pool);
+ removed_all &= trim_subnode_array(&node->pattern_sub_nodes->complex,
+ latest_any_var, scratch_pool);
+
+ /* Trim the tree as much as possible to speed up lookup(). */
+ if (removed_all)
+ node->pattern_sub_nodes = NULL;
+ }
+
+ return removed_all;
+}
+
+/* Forward declaration ... */
+static void
+finalize_tree(node_t *node,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool);
+
+/* Call finalize_tree() on all elements in the HASH of node_t *, passing
+ * SUM along. HASH may be NULL. Use SCRATCH_POOL for temporary allocations.
+ */
static void
-finalize_up_tree(node_t *parent,
- access_t *inherited_access,
- node_t *node,
- apr_pool_t *scratch_pool);
+finalize_subnode_hash(apr_hash_t *hash,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool)
+{
+ if (hash)
+ {
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(scratch_pool, hash);
+ hi;
+ hi = apr_hash_next(hi))
+ finalize_tree(apr_hash_this_val(hi), sum, scratch_pool);
+ }
+}
-/* Call finalize_up_tree() on all elements in the ARRAY of node_t *.
- * ARRAY may be NULL.
+/* Call finalize_up_tree() on all elements in the ARRAY of node_t *,
+ * passing SUM along. ARRAY may be NULL. Use SCRATCH_POOL for temporary
+ * allocations.
*/
static void
-finalize_up_subnode_array(node_t *parent,
- access_t *inherited_access,
- apr_array_header_t *array,
- apr_pool_t *scratch_pool)
+finalize_subnode_array(apr_array_header_t *array,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool)
{
if (array)
{
int i;
for (i = 0; i < array->nelts; ++i)
- finalize_up_tree(parent, inherited_access,
- APR_ARRAY_IDX(array, i, sorted_pattern_t).node,
- scratch_pool);
+ finalize_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node, sum,
+ scratch_pool);
}
}
@@ -544,57 +723,47 @@ link_prefix_patterns(apr_array_header_t
}
}
-/* Bottomp-up phase of the recursive update / finalization of the tree
- * node properties for NODE immediately below PARENT. The access rights
- * inherited from the parent path are given in INHERITED_ACCESS. None of
- * the pointers may be NULL. The tree root node may be used as its own
- * parent.
+/* Recursively finalization the tree node properties for NODE. Update SUM
+ * (of NODE's parent) by combining it with the recursive access rights info
+ * on NODE. Use SCRATCH_POOL for temporary allocations.
*/
static void
-finalize_up_tree(node_t *parent,
- access_t *inherited_access,
- node_t *node,
- apr_pool_t *scratch_pool)
-{
- /* Access rights at NODE. */
- access_t *access = has_local_rule(&node->rights)
- ? &node->rights.access
- : inherited_access;
-
- /* So far, min and max rights at NODE are the immediate access rights. */
- node->rights.min_rights = access->rights;
- node->rights.max_rights = access->rights;
+finalize_tree(node_t *node,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool)
+{
+ limited_rights_t *local_sum = &node->rights;
+
+ /* For convenience, we allow NODE to be NULL: */
+ if (!node)
+ return;
- /* Combine that information with sub-tree data. */
- if (node->sub_nodes)
+ /* Sum of rights at NODE - so far. */
+ if (has_local_rule(local_sum))
{
- apr_hash_index_t *hi;
- for (hi = apr_hash_first(scratch_pool, node->sub_nodes);
- hi;
- hi = apr_hash_next(hi))
- finalize_up_tree(node, access, apr_hash_this_val(hi),
- scratch_pool);
+ local_sum->max_rights = local_sum->access.rights;
+ local_sum->min_rights = local_sum->access.rights;
}
+ else
+ {
+ local_sum->min_rights = authz_access_write;
+ local_sum->max_rights = authz_access_none;
+ }
+
+ /* Process all sub-nodes. */
+ finalize_subnode_hash(node->sub_nodes, local_sum, scratch_pool);
- /* Do the same thing for all other sub-nodes as well. */
if (node->pattern_sub_nodes)
{
- if (node->pattern_sub_nodes->any)
- finalize_up_tree(node, access, node->pattern_sub_nodes->any,
- scratch_pool);
- if (node->pattern_sub_nodes->any_var)
- finalize_up_tree(node, access, node->pattern_sub_nodes->any_var,
- scratch_pool);
-
- finalize_up_subnode_array(node, access,
- node->pattern_sub_nodes->prefixes,
- scratch_pool);
- finalize_up_subnode_array(node, access,
- node->pattern_sub_nodes->suffixes,
- scratch_pool);
- finalize_up_subnode_array(node, access,
- node->pattern_sub_nodes->complex,
- scratch_pool);
+ finalize_tree(node->pattern_sub_nodes->any, local_sum, scratch_pool);
+ finalize_tree(node->pattern_sub_nodes->any_var, local_sum, scratch_pool);
+
+ finalize_subnode_array(node->pattern_sub_nodes->prefixes, local_sum,
+ scratch_pool);
+ finalize_subnode_array(node->pattern_sub_nodes->suffixes, local_sum,
+ scratch_pool);
+ finalize_subnode_array(node->pattern_sub_nodes->complex, local_sum,
+ scratch_pool);
/* Link up the prefixes / suffixes. */
link_prefix_patterns(node->pattern_sub_nodes->prefixes);
@@ -603,75 +772,7 @@ finalize_up_tree(node_t *parent,
/* Add our min / max info to the parent's info.
* Idempotent for parent == node (happens at root). */
- combine_right_limits(&parent->rights, &node->rights);
-}
-
-/* Forward declaration ... */
-static void
-finalize_down_tree(node_t *node,
- limited_rights_t rights,
- apr_pool_t *scratch_pool);
-
-/* Call finalize_down_tree() on all elements in the ARRAY of node_t *.
- * ARRAY may be NULL.
- */
-static void
-finalize_down_subnode_array(apr_array_header_t *array,
- const limited_rights_t *rights,
- apr_pool_t *scratch_pool)
-{
- if (array)
- {
- int i;
- for (i = 0; i < array->nelts; ++i)
- finalize_down_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node,
- *rights, scratch_pool);
- }
-}
-
-/* Top-down phase of the recursive update / finalization of the tree
- * node properties for NODE. The min / max access rights of all var-
- * segment rules that apply to the sub-tree of NODE are given in RIGHTS.
- */
-static void
-finalize_down_tree(node_t *node,
- limited_rights_t rights,
- apr_pool_t *scratch_pool)
-{
- /* Update the NODE's right limits. */
- combine_right_limits(&node->rights, &rights);
-
- /* If there are more var-segment rules, aggregate their rights as all
- * these rules are implictly repeated on all sub-nodes. */
- if (node->pattern_sub_nodes && node->pattern_sub_nodes->any_var)
- combine_right_limits(&rights, &node->pattern_sub_nodes->any_var->rights);
-
- /* Resurse into the sub-nodes. */
- if (node->sub_nodes)
- {
- apr_hash_index_t *hi;
- for (hi = apr_hash_first(scratch_pool, node->sub_nodes);
- hi;
- hi = apr_hash_next(hi))
- finalize_down_tree(apr_hash_this_val(hi), rights, scratch_pool);
- }
-
- if (node->pattern_sub_nodes)
- {
- if (node->pattern_sub_nodes->any)
- finalize_down_tree(node->pattern_sub_nodes->any, rights,
- scratch_pool);
- if (node->pattern_sub_nodes->any_var)
- finalize_down_tree(node->pattern_sub_nodes->any_var, rights,
- scratch_pool);
-
- finalize_down_subnode_array(node->pattern_sub_nodes->prefixes,
- &rights, scratch_pool);
- finalize_down_subnode_array(node->pattern_sub_nodes->suffixes,
- &rights, scratch_pool);
- finalize_down_subnode_array(node->pattern_sub_nodes->complex,
- &rights, scratch_pool);
- }
+ combine_right_limits(sum, local_sum);
}
/* From the authz CONFIG, extract the parts relevant to USER and REPOSITORY.
@@ -685,7 +786,6 @@ create_user_authz(svn_authz_t *authz,
apr_pool_t *scratch_pool)
{
int i;
- limited_rights_t var_rights;
node_t *root = create_node(NULL, result_pool);
construction_context_t *ctx = create_construction_context(scratch_pool);
@@ -705,14 +805,31 @@ create_user_authz(svn_authz_t *authz,
root->rights.access.rights = authz_access_none;
}
- /* Calculate recursive rights etc. */
+ /* Trim the tree.
+ *
+ * We can't do pattern comparison, so for most pattern rules we cannot
+ * say that a set of rules "eclipses" / overrides a given other set of
+ * rules for all possible paths. That limits the accuracy of our check
+ * for recursive access in similar ways than for non-pattern rules.
+ *
+ * However, the user expects a rule ending with "**" to eclipse any older
+ * rule in that sub-tree recursively. So, this trim function removes
+ * eclipsed nodes from the tree.
+ */
svn_pool_clear(subpool);
- finalize_up_tree(root, &root->rights.access, root, subpool);
+ trim_tree(root, NO_SEQUENCE_NUMBER, subpool);
+ /* Calculate recursive rights.
+ *
+ * This is a bottom-up calculation of the range of access rights
+ * specified anywhere in the respective sub-tree, including the base
+ * node itself.
+ *
+ * To prevent additional finalization passes, we piggy-back the addition
+ * of the ordering links of the prefix and suffix sub-node rules.
+ */
svn_pool_clear(subpool);
- var_rights.max_rights = authz_access_none;
- var_rights.min_rights = authz_access_write;
- finalize_down_tree(root, var_rights, subpool);
+ finalize_tree(root, &root->rights, subpool);
/* Done. */
svn_pool_destroy(subpool);
Modified: subversion/branches/authzperf/subversion/tests/libsvn_repos/repos-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_repos/repos-test.c?rev=1764337&r1=1764336&r2=1764337&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/tests/libsvn_repos/repos-test.c (original)
+++ subversion/branches/authzperf/subversion/tests/libsvn_repos/repos-test.c Tue Oct 11 20:08:44 2016
@@ -1536,6 +1536,243 @@ test_authz_prefixes(apr_pool_t *pool)
return SVN_NO_ERROR;
}
+static svn_error_t *
+test_authz_recursive_override(apr_pool_t *pool)
+{
+ svn_authz_t *authz_cfg;
+
+ /* Set all rights at some folder and replace them again. Make sure to
+ * cover the "/" b/c that already has an implicit rule, so we* overwrite
+ * it twice. The first 2 string placeholders in the rules are for the
+ * repository name and the optional glob support marker. */
+ const char *contents =
+ "[:glob:/A/B]" NL
+ "plato = rw" NL
+ "" NL
+ "[:glob:/A/**]" NL
+ "plato = r" NL
+ "" NL
+ "[:glob:/B/C]" NL
+ "plato =" NL
+ "" NL
+ "[:glob:/B/**]" NL
+ "plato = rw" NL
+ "" NL
+ "[:glob:/C/D]" NL
+ "plato = rw" NL
+ "" NL
+ "[:glob:/C/**/E]" NL
+ "plato = r" NL
+ "" NL
+ "[:glob:/D/E]" NL
+ "plato = r" NL
+ "" NL
+ "[:glob:/D/**/F]" NL
+ "plato = rw" NL;
+
+ /* Definition of the paths to test and expected replies for each. */
+ struct check_access_tests test_set[] = {
+ /* The root shall not be affected -> defaults to "no access". */
+ { "/", NULL, "plato", svn_authz_read, FALSE },
+ /* Recursive restriction of rights shall work. */
+ { "/A", NULL, "plato", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/A", NULL, "plato", svn_authz_write | svn_authz_recursive, FALSE },
+ /* Recursive extension of rights shall work. */
+ { "/B", NULL, "plato", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/B", NULL, "plato", svn_authz_write | svn_authz_recursive, TRUE },
+ /* Partial replacements shall not result in recursive rights. */
+ { "/C", NULL, "plato", svn_authz_read | svn_authz_recursive, FALSE },
+ { "/C/D", NULL, "plato", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/C/D", NULL, "plato", svn_authz_write | svn_authz_recursive, FALSE },
+ { "/D", NULL, "plato", svn_authz_read | svn_authz_recursive, FALSE },
+ { "/D/E", NULL, "plato", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/D/E", NULL, "plato", svn_authz_write | svn_authz_recursive, FALSE },
+ /* Sentinel */
+ { NULL, NULL, NULL, svn_authz_none, FALSE }
+ };
+
+ SVN_ERR(authz_get_handle(&authz_cfg, contents, FALSE, pool));
+
+ /* Loop over the test array and test each case. */
+ SVN_ERR(authz_check_access(authz_cfg, test_set, pool));
+
+ /* That's a wrap! */
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_authz_pattern_tests(apr_pool_t *pool)
+{
+ svn_authz_t *authz_cfg;
+
+ /* Rules will be considered for recursive access checks irrespective of
+ * whether the respective paths actually do exist. */
+ const char *contents =
+ "[:glob:/**/Yeti]" NL
+ "plato = r" NL
+ "" NL
+ "[/]" NL
+ "plato = r" NL
+ "" NL
+ "[/trunk]" NL
+ "plato = rw" NL;
+
+ /* Definition of the paths to test and expected replies for each. */
+ struct check_access_tests test_set[] = {
+ /* We have no recursive write access anywhere. */
+ { "/", NULL, "plato", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/", NULL, "plato", svn_authz_write | svn_authz_recursive, FALSE },
+ { "/trunk", NULL, "plato", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/trunk", NULL, "plato", svn_authz_write | svn_authz_recursive, FALSE },
+
+ /* We do have ordinary write access to anything under /trunk that is
+ * not a Yeti. */
+ { "/trunk", NULL, "plato", svn_authz_write, TRUE },
+ { "/trunk/A/B/C", NULL, "plato", svn_authz_write, TRUE },
+
+ /* We don't have write access to Yetis. */
+ { "/trunk/A/B/C/Yeti", NULL, "plato", svn_authz_write, FALSE },
+ { "/trunk/Yeti", NULL, "plato", svn_authz_write, FALSE },
+ { "/Yeti", NULL, "plato", svn_authz_write, FALSE },
+ /* Sentinel */
+ { NULL, NULL, NULL, svn_authz_none, FALSE }
+ };
+
+ const char *contents2 =
+ "[:glob:/X]" NL
+ "user1 =" NL
+ "" NL
+ "[:glob:/X/**]" NL
+ "user1 = rw" NL
+ "user2 = rw" NL
+ "" NL
+ "[:glob:/X/Y/Z]" NL
+ "user2 =" NL;
+
+ /* Definition of the paths to test and expected replies for each. */
+ struct check_access_tests test_set2[] = {
+ /* No access at the root*/
+ { "/", NULL, "user1", svn_authz_read, FALSE },
+ { "/", NULL, "user2", svn_authz_read, FALSE },
+
+ /* User 1 has recursive write access anywhere. */
+ { "/X", NULL, "user1", svn_authz_write | svn_authz_recursive, TRUE },
+ { "/X/Y", NULL, "user1", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/X/Y/Z", NULL, "user1", svn_authz_read | svn_authz_recursive, TRUE },
+
+ /* User 2 only has recursive read access to X/Y/Z. */
+ { "/X", NULL, "user1", svn_authz_read | svn_authz_recursive, TRUE },
+ { "/X", NULL, "user2", svn_authz_write | svn_authz_recursive, FALSE },
+ { "/X/Y", NULL, "user2", svn_authz_write | svn_authz_recursive, FALSE },
+ { "/X/Y/Z", NULL, "user2", svn_authz_write | svn_authz_recursive, FALSE },
+
+ /* However, user2 has ordinary write access X and recursive write access
+ * to anything not in X/Y/Z. */
+ { "/X", NULL, "user2", svn_authz_write, TRUE },
+ { "/X/A", NULL, "user2", svn_authz_write | svn_authz_recursive, TRUE },
+ { "/X/Y/A", NULL, "user2", svn_authz_write | svn_authz_recursive, TRUE },
+
+ /* Sentinel */
+ { NULL, NULL, NULL, svn_authz_none, FALSE }
+ };
+
+ const char *contents3 =
+ "[groups]" NL
+ "Team1 = user1" NL
+ "Team2 = user1, user2" NL
+ "" NL
+ "[/]" NL
+ "* =" NL
+ "" NL
+ "[:glob:Repo1:/**/folder*]" NL
+ "@Team1 = rw" NL
+ "" NL
+ "[Repo2:/]" NL
+ "@Team2 = r" NL;
+
+ /* Definition of the paths to test and expected replies for each. */
+ struct check_access_tests test_set3[] = {
+ /* No access at the root*/
+ { "/", "Repo1", "user1", svn_authz_read, FALSE },
+ { "/", "Repo1", "user2", svn_authz_read, FALSE },
+ { "/", "Repo2", "user1", svn_authz_read, TRUE },
+ { "/", "Repo2", "user2", svn_authz_read, TRUE },
+ { "/", "Repo2", "user1", svn_authz_write, FALSE },
+ { "/", "Repo2", "user2", svn_authz_write, FALSE },
+
+ /* User 1 has recursive write access anywhere. */
+ { "/folder_1", "Repo1", "user1",
+ svn_authz_write | svn_authz_recursive, TRUE },
+ { "/folder_1", "Repo1", "user2", svn_authz_read, FALSE },
+ { "/1_folder", "Repo1", "user1", svn_authz_read, FALSE },
+ { "/foo/bar/folder_2/random", "Repo1", "user1",
+ svn_authz_write | svn_authz_recursive, TRUE },
+ { "/foo/bar/folder_2/random", "Repo1", "user2", svn_authz_read, FALSE },
+ { "/foo/bar/2_folder/random", "Repo1", "user1", svn_authz_read, FALSE },
+ { "/foo/bar/folder", "Repo1", "user1",
+ svn_authz_write | svn_authz_recursive, TRUE },
+ { "/foo/bar/folder", "Repo1", "user2", svn_authz_read, FALSE },
+ { "/foo/bar/folde", "Repo1", "user1", svn_authz_read, FALSE },
+ { "/foo/bar/folde", "Repo1", "user2", svn_authz_read, FALSE },
+
+ /* Sentinel */
+ { NULL, NULL, NULL, svn_authz_none, FALSE }
+ };
+
+ const char *contents4 =
+ "[groups]" NL
+ "team1 = user1, user3" NL
+ "team2 = user2, user3" NL
+ "" NL
+ "[:glob:Repo1:/trunk/**]" NL
+ "@team2 = rw" NL
+ "" NL
+ "[:glob:Repo1:/trunk/*]" NL
+ "@team1 = r" NL;
+
+ /* Definition of the paths to test and expected replies for each. */
+ struct check_access_tests test_set4[] = {
+ /* Team2 has r/w access to /trunk */
+ { "/trunk", "Repo1", "user1", svn_authz_read, FALSE },
+ { "/trunk", "Repo1", "user2", svn_authz_write, TRUE },
+ { "/trunk", "Repo1", "user3", svn_authz_write, TRUE },
+
+ /* At the first sub-level, team1 has only read access;
+ * the remainder of team2 has write access. */
+ { "/trunk/A", "Repo1", "user1", svn_authz_read, TRUE },
+ { "/trunk/A", "Repo1", "user3", svn_authz_read, TRUE },
+ { "/trunk/A", "Repo1", "user1", svn_authz_write, FALSE },
+ { "/trunk/A", "Repo1", "user2", svn_authz_write, TRUE },
+ { "/trunk/A", "Repo1", "user3", svn_authz_write, FALSE },
+
+ /* At the second sub-level, team2 has full write access;
+ * the remainder of team1 has still r/o access. */
+ { "/trunk/A/B", "Repo1", "user2", svn_authz_write, TRUE },
+ { "/trunk/A/B", "Repo1", "user3", svn_authz_write, TRUE },
+ { "/trunk/A/B", "Repo1", "user1", svn_authz_read, TRUE },
+ { "/trunk/A/B", "Repo1", "user1", svn_authz_write, FALSE },
+
+ /* Sentinel */
+ { NULL, NULL, NULL, svn_authz_none, FALSE }
+ };
+
+ /* Verify that the rules are applies as expected. */
+ SVN_ERR(authz_get_handle(&authz_cfg, contents, FALSE, pool));
+ SVN_ERR(authz_check_access(authz_cfg, test_set, pool));
+
+ SVN_ERR(authz_get_handle(&authz_cfg, contents2, FALSE, pool));
+ SVN_ERR(authz_check_access(authz_cfg, test_set2, pool));
+
+ SVN_ERR(authz_get_handle(&authz_cfg, contents3, FALSE, pool));
+ SVN_ERR(authz_check_access(authz_cfg, test_set3, pool));
+
+ SVN_ERR(authz_get_handle(&authz_cfg, contents4, FALSE, pool));
+ SVN_ERR(authz_check_access(authz_cfg, test_set4, pool));
+
+ /* That's a wrap! */
+ return SVN_NO_ERROR;
+}
+
/* Test in-repo authz paths */
static svn_error_t *
@@ -4088,6 +4325,10 @@ static struct svn_test_descriptor_t test
"test committing a previously aborted txn"),
SVN_TEST_PASS2(test_authz_prefixes,
"test authz prefixes"),
+ SVN_TEST_PASS2(test_authz_recursive_override,
+ "test recursively authz rule override"),
+ SVN_TEST_PASS2(test_authz_pattern_tests,
+ "test various basic authz pattern combinations"),
SVN_TEST_NULL
};