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
   };
 


Reply via email to