On 02/12/14 16:49, C. Michael Pilato wrote:
On 11/28/2014 12:03 PM, Hannes Reich wrote:
I'd like to suggest an extension to the authz file format to support the
following scenario:

* Some users of the repository should have access to everything, others
   should be restricted to a small set of "public" directories.

* All users should be able to check out from the same "root" directory.

I love the idea of the feature, and in fact began at one point in the
past trying to provide similar functionality on the authz-overhaul
branch.

Thanks for the encouragement and background information.

I've attached the patch, which I would describe as a workaround for the underlying issue the authz-overhaul branch is addressing.

Since the patch lacks authz-overhaul's concept of "list access", the "ancestor" permission has some side-effects:

- Users can learn of the existence of the siblings of all ancestors of paths to which they have access (by poking around in .svn/wc.db). This is suboptimal but acceptable for my use case.

- Users can access the properties of all ancestors of paths to which they have access. Perhaps this can be construed as a feature since it enables access to svn:mergeinfo, though I haven't explored how well merges as a restricted user work in practice.

/Hannes
[[[
Add support for ancestor path permission in authz files

Extends the authz file format with the ability to specify a permission that 
should apply to a directory path and all directories above it. This is useful 
to permit users to check out from the root of a repository where they have 
limited read access.

* subversion/libsvn_repos/authz.c
  (authz_lookup_baton): Add a "required_for_ancestor" flag, to distinguish 
lookups for permissions that can be applied to ancestors of the specified path.
  (authz_parse_line, authz_validate_rule): Implement the new authz syntax: 
Permission settings may contain a "a" character, to indicate that the 
permission applies to all containing directories.
  (authz_parse_section, authz_get_tree_access): Implement a 
"required_for_ancestor" code path that searches for lines that grant ancestor 
permissions.
  (svn_repos_authz_check_access): When checking for non-recursive access 
permissions, look for ancestor permission on subpaths.

* subversion/tests/cmdline/authz_tests.py
  (authz_recursive_ls): Add a test case featuring ancestor permissions.

* subversion/tests/libsvn_repos/repos-test.c
  (authz): Add a test for ancestor permissions.

]]]

Index: subversion/libsvn_repos/authz.c
===================================================================
--- subversion/libsvn_repos/authz.c     (revision 1644608)
+++ subversion/libsvn_repos/authz.c     (working copy)
@@ -65,6 +65,10 @@ struct authz_lookup_baton {
   /* repos_path prefixed by the repository name and a colon. */
   const char *qualified_repos_path;
 
+  /* Whether a recursive lookup is searching for permissions to apply
+     to a parent path. */
+  svn_boolean_t required_for_ancestors;
+
   /* Whether, at the end of a recursive lookup, access is granted. */
   svn_boolean_t access;
 };
@@ -264,6 +268,11 @@ authz_parse_line(const char *name, const char *val
   if (!authz_line_applies_to_user(name, b, pool))
     return TRUE;
 
+  /* Stop if we are looking for ancestor permissions, but the rule is
+     not applicable to ancestors. */
+  if (b->required_for_ancestors && !strchr(value, 'a'))
+    return TRUE;
+
   /* Set the access grants for the rule. */
   if (strchr(value, 'r'))
     b->allow |= svn_authz_read;
@@ -303,7 +312,7 @@ static svn_boolean_t
 authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
 {
   struct authz_lookup_baton *b = baton;
-  svn_boolean_t conclusive;
+  svn_boolean_t conclusive, granted;
 
   /* Does the section apply to us? */
   if (!is_applicable_section(b->qualified_repos_path, section_name)
@@ -319,13 +328,22 @@ authz_parse_section(const char *section_name, void
   conclusive = authz_access_is_determined(b->allow, b->deny,
                                           b->required_access);
 
-  /* Is access granted OR inconclusive? */
-  b->access = authz_access_is_granted(b->allow, b->deny,
-                                      b->required_access)
-    || !conclusive;
-
-  /* As long as access isn't conclusively denied, carry on. */
-  return b->access;
+  /* Is access granted? */
+  granted = authz_access_is_granted(b->allow, b->deny,
+                                    b->required_access);
+  if (b->required_for_ancestors)
+  {
+    /* Continue as long as we don't find a determined, granted access. */
+    b->access = granted && conclusive;
+    return !b->access;
+  }
+  else
+  {
+    /* Is access granted OR inconclusive? */
+    b->access = granted || !conclusive;
+    /* As long as access isn't conclusively denied, carry on. */
+    return b->access;
+  }
 }
 
 
@@ -386,6 +404,7 @@ static svn_boolean_t
 authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
                       const char *path, const char *user,
                       svn_repos_authz_access_t required_access,
+                      svn_boolean_t required_for_ancestors,
                       apr_pool_t *pool)
 {
   struct authz_lookup_baton baton = { 0 };
@@ -393,11 +412,13 @@ authz_get_tree_access(svn_config_t *cfg, const cha
   baton.config = cfg;
   baton.user = user;
   baton.required_access = required_access;
+  baton.required_for_ancestors = required_for_ancestors;
   baton.repos_path = path;
   baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
                                            ":", path, SVN_VA_NULL);
-  /* Default to access granted if no rules say otherwise. */
-  baton.access = TRUE;
+  if(!required_for_ancestors)
+    /* Default to access granted if no rules say otherwise. */
+    baton.access = TRUE;
 
   svn_config_enumerate_sections2(cfg, authz_parse_section,
                                  &baton, pool);
@@ -562,7 +583,7 @@ authz_group_walk(svn_config_t *cfg,
  *   doing it more than once within the one rule, and that it isn't
  *   "~*", as that would never match.
  * - Check that VALUE part of the rule specifies only allowed rule
- *   flag characters ('r' and 'w').
+ *   flag characters ('r', 'w' and 'a').
  *
  * Return TRUE if the rule has no errors. Use BATON for context and
  * error reporting.
@@ -660,7 +681,8 @@ static svn_boolean_t authz_validate_rule(const cha
 
   while (*val)
     {
-      if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
+      if (*val != 'r' && *val != 'w' && *val != 'a'
+          && ! svn_ctype_isspace(*val))
         {
           b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                      "The character '%c' in rule '%s' is not "
@@ -1056,7 +1078,7 @@ svn_repos_authz_check_access(svn_authz_t *authz, c
         {
           /* Deny access by default. */
           *access_granted = FALSE;
-          return SVN_NO_ERROR;
+          break;
         }
 
       /* Work back to the parent path. */
@@ -1068,7 +1090,15 @@ svn_repos_authz_check_access(svn_authz_t *authz, c
      to the requested user. */
   if (*access_granted && (required_access & svn_authz_recursive))
     *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
-                                            user, required_access, pool);
+                                            user, required_access, FALSE,
+                                            pool);
 
+  /* If the caller did not request recursive access, then a child path
+     could still allow us to grant access. */
+  if (!*access_granted && !(required_access & svn_authz_recursive))
+    *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
+                                            user, required_access, TRUE,
+                                            pool);
+
   return SVN_NO_ERROR;
 }
Index: subversion/tests/cmdline/authz_tests.py
===================================================================
--- subversion/tests/cmdline/authz_tests.py     (revision 1644608)
+++ subversion/tests/cmdline/authz_tests.py     (working copy)
@@ -128,7 +128,7 @@ def broken_authz_file(sbox):
 
   sbox.build(create_wc = False)
 
-  # No characters but 'r', 'w', and whitespace are allowed as a value
+  # No characters but 'r', 'w', 'R', and whitespace are allowed as a value
   # in an authz rule.
   write_authz_file(sbox, {"/": "jrandom = rw  # End-line comments disallowed"})
 
@@ -1143,6 +1143,28 @@ def authz_recursive_ls(sbox):
                                      [], 'ls', '-R',
                                      sbox.repo_url)
 
+  write_authz_file(sbox, {'/'       : '* =',
+                          '/A/B/E'  : '* =',
+                          # These two enable traversal from / to the directory
+                          '/A/B'       : '* = ra',
+                          '/A/D/G/rho' : '* = rwa',
+                          # This one does not
+                          '/A/D/H/psi' : '* = r',
+                          })
+  expected_entries = [
+    'A/',
+    'A/B/',
+    'A/B/F/',
+    'A/B/lambda',
+    'A/D/',
+    'A/D/G/',
+    'A/D/G/rho',
+    ]
+  svntest.actions.run_and_verify_svn('recursive ls from /',
+                                     map(lambda x: x + '\n', expected_entries),
+                                     [], 'ls', '-R',
+                                     sbox.repo_url)
+
 @Issue(3781)
 @Skip(svntest.main.is_ra_type_file)
 def case_sensitive_authz(sbox):
Index: subversion/tests/libsvn_repos/repos-test.c
===================================================================
--- subversion/tests/libsvn_repos/repos-test.c  (revision 1644608)
+++ subversion/tests/libsvn_repos/repos-test.c  (working copy)
@@ -1257,6 +1257,10 @@ authz(apr_pool_t *pool)
        of subpaths. */
     { "/A/D", "greek", "plato", svn_authz_read | svn_authz_recursive, TRUE },
     { "/A/D", "greek", NULL, svn_authz_read | svn_authz_recursive, FALSE },
+    /* Test that ancestor access grants non-recursive access to
+       containing directories. */
+    { "/H", "greek", NULL, svn_authz_read, TRUE },
+    { "/H", "greek", NULL, svn_authz_read | svn_authz_recursive, FALSE },
     /* Test global write access lookups. */
     { NULL, "greek", "plato", svn_authz_read, TRUE },
     { NULL, "greek", NULL, svn_authz_write, FALSE },
@@ -1308,6 +1312,9 @@ authz(apr_pool_t *pool)
     "[greek:/A/B/E/beta]"                                                    NL
     "* ="                                                                    NL
     ""                                                                       NL
+    "[greek:/H/I/J]"                                                         NL
+    "* = ra"                                                                 NL
+    ""                                                                       NL
     "[/nowhere]"                                                             NL
     "nobody = r"                                                             NL
     ""                                                                       
NL;

Reply via email to