[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#795447: stretch-pu: package subversion/1.8.13-1+deb9u1



Package: release.debian.org
Severity: normal

The recent upstream Subversion releases included fixes for 2 CVEs
(CVE-2015-3184 and CVE-2015-3187).  The sid upload got caught up in the
libstdc++ transition, so I've uploaded an updated package for stretch.

Attached is the debdiff.  It's large, but a good chunk of that is added
testing for the security fix.

Cheers,
James

diffstat for subversion_1.8.13-1 subversion_1.8.13-1+deb9u1

 debian/patches/CVE-2015-3184            | 2165 ++++++++++++++++++++++++++++++++
 debian/patches/CVE-2015-3187            |  404 +++++
 subversion-1.8.13/debian/changelog      |   10 
 subversion-1.8.13/debian/control        |    4 
 subversion-1.8.13/debian/patches/series |    2 
 5 files changed, 2583 insertions(+), 2 deletions(-)

-- System Information:
Debian Release: stretch/sid
  APT prefers unstable
  APT policy: (500, 'unstable'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 4.0.0-2-amd64 (SMP w/4 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)
diffstat for subversion_1.8.13-1 subversion_1.8.13-1+deb9u1

 debian/patches/CVE-2015-3184            | 2165 ++++++++++++++++++++++++++++++++
 debian/patches/CVE-2015-3187            |  404 +++++
 subversion-1.8.13/debian/changelog      |   10 
 subversion-1.8.13/debian/control        |    4 
 subversion-1.8.13/debian/patches/series |    2 
 5 files changed, 2583 insertions(+), 2 deletions(-)

diff -u subversion-1.8.13/debian/changelog subversion-1.8.13/debian/changelog
--- subversion-1.8.13/debian/changelog
+++ subversion-1.8.13/debian/changelog
@@ -1,3 +1,13 @@
+subversion (1.8.13-1+deb9u1) stretch; urgency=medium
+
+  * Add (Build-)Depends on apache2 packages necessary for security fixes.
+  * patches/CVE-2015-3814: Mixed anonymous/authenticated path-based authz with
+    httpd 2.4
+  * patches/CVE-2015-3817: svn_repos_trace_node_locations() reveals path
+    hidden by authz
+
+ -- James McCoy <jamessan@debian.org>  Wed, 12 Aug 2015 20:31:26 -0400
+
 subversion (1.8.13-1) unstable; urgency=medium
 
   * New upstream release.  Refresh patches.
diff -u subversion-1.8.13/debian/control subversion-1.8.13/debian/control
--- subversion-1.8.13/debian/control
+++ subversion-1.8.13/debian/control
@@ -6,7 +6,7 @@
            James McCoy <jamessan@debian.org>
 Build-Depends: debhelper (>= 8), libserf-dev (>= 1.2), zlib1g-dev,
                libapr1-dev, libaprutil1-dev, libdb5.3-dev,
-               libsasl2-dev, apache2-dev, dh-apache2,
+               libsasl2-dev, apache2-dev (>= 2.4.16), dh-apache2,
                libsqlite3-dev (>= 3.7.12), libgnome-keyring-dev, libdbus-1-dev, kdelibs5-dev,
                quilt, doxygen, autotools-dev, autoconf, libtool-bin, swig,
                python-all-dev, perl, libperl-dev, ruby, ruby-dev,
@@ -83,7 +83,7 @@
 Package: libapache2-mod-svn
 Section: httpd
 Architecture: any
-Depends: apache2-api-20120211, libsvn1 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
+Depends: apache2-bin (>= 2.4.16), apache2-api-20120211, libsvn1 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
 Breaks: libapache2-svn (<< 1.7.9-1+nmu5)
 Replaces: libapache2-svn (<< 1.7.9-1+nmu5)
 Suggests: db5.3-util
diff -u subversion-1.8.13/debian/patches/series subversion-1.8.13/debian/patches/series
--- subversion-1.8.13/debian/patches/series
+++ subversion-1.8.13/debian/patches/series
@@ -19,0 +20,2 @@
+CVE-2015-3187
+CVE-2015-3184
only in patch2:
unchanged:
--- subversion-1.8.13.orig/debian/patches/CVE-2015-3184
+++ subversion-1.8.13/debian/patches/CVE-2015-3184
@@ -0,0 +1,2165 @@
+  Mixed anonymous/authenticated path-based authz with Apache httpd 2.4.
+
+Summary
+=======
+
+  Subversion's mod_authz_svn does not properly restrict anonymous
+  access in some mixed anonymous/authenticated environments when using
+  Apache httpd 2.4.  The result is that anonymous access may be possible
+  to files for which only authenticated access should be possible.
+
+Known vulnerable
+================
+
+  Apache httpd 2.4.0 to 2.4.12
+  Apache Subversion 1.8.0 to 1.8.13
+  Apache Subversion 1.7.0 to 1.7.20
+
+  Servers are vulnerable if either httpd or Subversion is as listed.
+
+  Subversion 1.6 does not build with httpd 2.4 and servers using
+  httpd 2.2 are not vulnerable.  Servers that are configured to deny
+  anonymous access are not vulnerable.
+
+Known fixed
+===========
+
+  Apache httpd 2.4.16
+  Apache Subversion 1.8.14 and 1.7.21
+
+  Both httpd and Subversion need to be updated.  Subversion must
+  be built with a fixed httpd.
+
+Details
+=======
+
+  If you have a Subversion repository configured for anonymous read
+  that has mod_authz_svn configured such that some portion of the
+  repository is hidden from an anonymous user, then in certain cases
+  when Subversion is used with Apache httpd 2.4.x the file contents of
+  the repository may be exposed to someone who knows the path name
+  within the repository.  The protected files and directories will not
+  show on directory listings.  Protected directories that do not show
+  in their parent will return an empty directory listing rather than a
+  403 error.  Protected files will return the full content of the
+  file.  Specifically the conditions required for this to happen is
+  that there needs to be a <Directory> block for the DocumentRoot
+  allowing access to everyone (e.g. Require all granted) and "Satisfy
+  any" must not be set.  This sort of configuration is included in the
+  default httpd.conf that `make install` provides and is fairly
+  standard.
+
+Severity
+========
+
+  CVSSv2 Base Score: 4.3
+  CVSSv2 Base Vector: AV:N/AC:M/Au:N/C:P/I:N/A:N
+
+  The repository needs to be configured with mixed anonymous and
+  authenticated path-based authz and the the attacker needs to know
+  the paths to files in the repository that require authentication.
+
+Recommendations
+===============
+
+  All users of mixed anonymous/authenticated authz to upgrade Apache
+  and Subversion.
+
+  As a workaround disable mixed anonymous/authenticated authz.
+
+References
+==========
+
+  CVE-2015-3184 (Subversion)
+  CVE-2015-3185 (httpd)
+
+Reported by
+===========
+
+  C. Michael Pilato, CollabNet
+
+Index: Makefile.in
+===================================================================
+--- a/Makefile.in	(revision 1691883)
++++ b/Makefile.in	(working copy)
+@@ -358,6 +358,7 @@ TEST_SHLIB_VAR_SWIG_RB=\
+   fi;
+ 
+ APXS = @APXS@
++HTTPD_VERSION = @HTTPD_VERSION@
+ 
+ PYTHON = @PYTHON@
+ PERL = @PERL@
+@@ -510,6 +511,9 @@ check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $(TEST_DEPS
+ 	  if test "$(HTTP_LIBRARY)" != ""; then                              \
+ 	    flags="--http-library $(HTTP_LIBRARY) $$flags";                  \
+ 	  fi;                                                                \
++	  if test "$(HTTPD_VERSION)" != ""; then                              \
++	    flags="--httpd-version $(HTTPD_VERSION) $$flags";                  \
++	  fi;                                                                \
+ 	  if test "$(SERVER_MINOR_VERSION)" != ""; then                      \
+ 	    flags="--server-minor-version $(SERVER_MINOR_VERSION) $$flags";  \
+ 	  fi;                                                                \
+Index: build/ac-macros/apache.m4
+===================================================================
+--- a/build/ac-macros/apache.m4	(revision 1691883)
++++ b/build/ac-macros/apache.m4	(working copy)
+@@ -160,6 +160,20 @@ if test -n "$APXS" && test "$APXS" != "no"; then
+     BUILD_APACHE_RULE=apache-mod
+     INSTALL_APACHE_RULE=install-mods-shared
+     INSTALL_APACHE_MODS=true
++    HTTPD="`$APXS -q sbindir`/`$APXS -q PROGNAME`"
++    if ! test -e $HTTPD ; then
++      HTTPD="`$APXS -q bindir`/`$APXS -q PROGNAME`"
++    fi
++    HTTPD_VERSION=["`$HTTPD -v | $SED -e 's@^.*/\([0-9.]*\)\(.*$\)@\1@ ; 1q'`"]
++    AC_ARG_ENABLE(broken-httpd-auth,
++      AS_HELP_STRING([--enable-broken-httpd-auth],
++                     [Allow building against httpd 2.4 with broken auth]),
++      [broken_httpd_auth=$enableval],[broken_httpd_auth=no])
++    if test "$enable_broken_httpd_auth" = "yes"; then
++      AC_MSG_NOTICE([Building with broken httpd auth])
++      AC_DEFINE(SVN_ALLOW_BROKEN_HTTPD_AUTH, 1,
++                [Defined to allow building against httpd 2.4 with broken auth])
++    fi
+ 
+     case $host in
+       *-*-cygwin*)
+@@ -178,6 +192,7 @@ AC_SUBST(APACHE_LDFLAGS)
+ AC_SUBST(APACHE_INCLUDES)
+ AC_SUBST(APACHE_LIBEXECDIR)
+ AC_SUBST(INSTALL_APACHE_MODS)
++AC_SUBST(HTTPD_VERSION)
+ 
+ # there aren't any flags that interest us ...
+ #if test -n "$APXS" && test "$APXS" != "no"; then
+Index: build/run_tests.py
+===================================================================
+--- a/build/run_tests.py	(revision 1691883)
++++ b/build/run_tests.py	(working copy)
+@@ -29,6 +29,7 @@
+             [--fs-type=<fs-type>] [--fsfs-packing] [--fsfs-sharding=<n>]
+             [--list] [--milestone-filter=<regex>] [--mode-filter=<type>]
+             [--server-minor-version=<version>] [--http-proxy=<host>:<port>]
++            [--httpd-version=<version>]
+             [--config-file=<file>] [--ssl-cert=<file>]
+             <abs_srcdir> <abs_builddir>
+             <prog ...>
+@@ -125,7 +126,7 @@ class TestHarness:
+                fsfs_sharding=None, fsfs_packing=None,
+                list_tests=None, svn_bin=None, mode_filter=None,
+                milestone_filter=None, set_log_level=None, ssl_cert=None,
+-               http_proxy=None):
++               http_proxy=None, httpd_version=None):
+     '''Construct a TestHarness instance.
+ 
+     ABS_SRCDIR and ABS_BUILDDIR are the source and build directories.
+@@ -178,6 +179,7 @@ class TestHarness:
+     self.log = None
+     self.ssl_cert = ssl_cert
+     self.http_proxy = http_proxy
++    self.httpd_version = httpd_version
+     if not sys.stdout.isatty() or sys.platform == 'win32':
+       TextColors.disable()
+ 
+@@ -481,6 +483,8 @@ class TestHarness:
+       svntest.main.options.ssl_cert = self.ssl_cert
+     if self.http_proxy is not None:
+       svntest.main.options.http_proxy = self.http_proxy
++    if self.httpd_version is not None:
++      svntest.main.options.httpd_version = self.httpd_version
+ 
+     svntest.main.options.srcdir = self.srcdir
+ 
+@@ -645,7 +649,7 @@ def main():
+                             'enable-sasl', 'parallel', 'config-file=',
+                             'log-to-stdout', 'list', 'milestone-filter=',
+                             'mode-filter=', 'set-log-level=', 'ssl-cert=',
+-                            'http-proxy='])
++                            'http-proxy=', 'httpd-version='])
+   except getopt.GetoptError:
+     args = []
+ 
+@@ -656,9 +660,9 @@ def main():
+   base_url, fs_type, verbose, cleanup, enable_sasl, http_library, \
+     server_minor_version, fsfs_sharding, fsfs_packing, parallel, \
+     config_file, log_to_stdout, list_tests, mode_filter, milestone_filter, \
+-    set_log_level, ssl_cert, http_proxy = \
++    set_log_level, ssl_cert, http_proxy, httpd_version = \
+             None, None, None, None, None, None, None, None, None, None, None, \
+-            None, None, None, None, None, None, None
++            None, None, None, None, None, None, None, None
+   for opt, val in opts:
+     if opt in ['-u', '--url']:
+       base_url = val
+@@ -696,6 +700,8 @@ def main():
+       ssl_cert = val
+     elif opt in ['--http-proxy']:
+       http_proxy = val
++    elif opt in ['--httpd-version']:
++      httpd_version = val
+     else:
+       raise getopt.GetoptError
+ 
+@@ -712,7 +718,7 @@ def main():
+                    fsfs_sharding, fsfs_packing, list_tests,
+                    mode_filter=mode_filter, milestone_filter=milestone_filter,
+                    set_log_level=set_log_level, ssl_cert=ssl_cert,
+-                   http_proxy=http_proxy)
++                   http_proxy=http_proxy, httpd_version=httpd_version)
+ 
+   failed = th.run(args[2:])
+   if failed:
+Index: subversion/mod_authz_svn/mod_authz_svn.c
+===================================================================
+--- a/subversion/mod_authz_svn/mod_authz_svn.c	(revision 1691883)
++++ b/subversion/mod_authz_svn/mod_authz_svn.c	(working copy)
+@@ -48,6 +48,23 @@
+ #include "svn_dirent_uri.h"
+ #include "private/svn_fspath.h"
+ 
++/* The apache headers define these and they conflict with our definitions. */
++#ifdef PACKAGE_BUGREPORT
++#undef PACKAGE_BUGREPORT
++#endif
++#ifdef PACKAGE_NAME
++#undef PACKAGE_NAME
++#endif
++#ifdef PACKAGE_STRING
++#undef PACKAGE_STRING
++#endif
++#ifdef PACKAGE_TARNAME
++#undef PACKAGE_TARNAME
++#endif
++#ifdef PACKAGE_VERSION
++#undef PACKAGE_VERSION
++#endif
++#include "svn_private_config.h"
+ 
+ #ifdef APLOG_USE_MODULE
+ APLOG_USE_MODULE(authz_svn);
+@@ -67,6 +84,30 @@ typedef struct authz_svn_config_rec {
+   const char *force_username_case;
+ } authz_svn_config_rec;
+ 
++#if AP_MODULE_MAGIC_AT_LEAST(20060110,0) /* version where
++                                            ap_some_auth_required breaks */
++#  if AP_MODULE_MAGIC_AT_LEAST(20120211,47) /* first version with
++                                               force_authn hook and
++                                               ap_some_authn_required() which
++                                               allows us to work without
++                                               ap_some_auth_required() */
++#    define USE_FORCE_AUTHN 1
++#    define IN_SOME_AUTHN_NOTE "authz_svn-in-some-authn"
++#    define FORCE_AUTHN_NOTE "authz_svn-force-authn"
++#  else
++     /* ap_some_auth_required() is busted and no viable alternative exists */
++#    ifndef SVN_ALLOW_BROKEN_HTTPD_AUTH
++#      error This version of httpd has a security hole with mod_authz_svn
++#    else
++       /* user wants to build anyway */
++#      define USE_FORCE_AUTHN 0
++#    endif
++#  endif
++#else
++   /* old enough that ap_some_auth_required() still works */
++#  define USE_FORCE_AUTHN 0
++#endif
++
+ #if DAV_SVN_LOAD_OPTIONAL
+ static APR_OPTIONAL_FN_TYPE(dav_svn_get_repos_path) *get_repos_path;
+ static APR_OPTIONAL_FN_TYPE(dav_svn_split_uri) *split_uri;
+@@ -826,9 +867,51 @@ access_checker(request_rec *r)
+                                                     &authz_svn_module);
+   const char *repos_path = NULL;
+   const char *dest_repos_path = NULL;
+-  int status;
++  int status, authn_required;
+ 
++#if USE_FORCE_AUTHN
++  /* Use the force_authn() hook available in 2.4.x to work securely
++   * given that ap_some_auth_required() is no longer functional for our
++   * purposes in 2.4.x.
++   */
++  int authn_configured;
++
+   /* We are not configured to run */
++  if (!conf->anonymous || apr_table_get(r->notes, IN_SOME_AUTHN_NOTE)
++      || (! (conf->access_file || conf->repo_relative_access_file)))
++    return DECLINED;
++
++  /* Authentication is configured */
++  authn_configured = ap_auth_type(r) != NULL;
++  if (authn_configured)
++    {
++      /* If the user is trying to authenticate, let him.  It doesn't
++       * make much sense to grant anonymous access but deny authenticated
++       * users access, even though you can do that with '$anon' in the
++       * access file.
++       */
++      if (apr_table_get(r->headers_in,
++                        (PROXYREQ_PROXY == r->proxyreq)
++                        ? "Proxy-Authorization" : "Authorization"))
++        {
++          /* Set the note to force authn regardless of what access_checker_ex
++             hook requires */
++          apr_table_setn(r->notes, FORCE_AUTHN_NOTE, (const char*)1);
++
++          /* provide the proper return so the access_checker hook doesn't
++           * prevent the code from continuing on to the other auth hooks */
++          if (ap_satisfies(r) != SATISFY_ANY)
++            return OK;
++          else
++            return HTTP_FORBIDDEN;
++        }
++    }    
++
++#else
++  /* Support for older versions of httpd that have a working
++   * ap_some_auth_required() */
++
++  /* We are not configured to run */
+   if (!conf->anonymous
+       || (! (conf->access_file || conf->repo_relative_access_file)))
+     return DECLINED;
+@@ -841,9 +924,10 @@ access_checker(request_rec *r)
+       if (ap_satisfies(r) != SATISFY_ANY)
+         return DECLINED;
+ 
+-      /* If the user is trying to authenticate, let him.  If anonymous
+-       * access is allowed, so is authenticated access, by definition
+-       * of the meaning of '*' in the access file.
++      /* If the user is trying to authenticate, let him.  It doesn't
++       * make much sense to grant anonymous access but deny authenticated
++       * users access, even though you can do that with '$anon' in the
++       * access file.
+        */
+       if (apr_table_get(r->headers_in,
+                         (PROXYREQ_PROXY == r->proxyreq)
+@@ -855,6 +939,7 @@ access_checker(request_rec *r)
+           return HTTP_FORBIDDEN;
+         }
+     }
++#endif
+ 
+   /* If anon access is allowed, return OK */
+   status = req_check_access(r, conf, &repos_path, &dest_repos_path);
+@@ -863,7 +948,26 @@ access_checker(request_rec *r)
+       if (!conf->authoritative)
+         return DECLINED;
+ 
++#if USE_FORCE_AUTHN
++      if (authn_configured) {
++          /* We have to check to see if authn is required because if so we must
++           * return UNAUTHORIZED (401) rather than FORBIDDEN (403) since returning
++           * the 403 leaks information about what paths may exist to
++           * unauthenticated users.  We must set a note here in order
++           * to use ap_some_authn_rquired() without triggering an infinite
++           * loop since the call will trigger this function to be called again. */
++          apr_table_setn(r->notes, IN_SOME_AUTHN_NOTE, (const char*)1);
++          authn_required = ap_some_authn_required(r);
++          apr_table_unset(r->notes, IN_SOME_AUTHN_NOTE);
++          if (authn_required)
++            {
++              ap_note_auth_failure(r);
++              return HTTP_UNAUTHORIZED;
++            }
++      }
++#else
+       if (!ap_some_auth_required(r))
++#endif
+         log_access_verdict(APLOG_MARK, r, 0, repos_path, dest_repos_path);
+ 
+       return HTTP_FORBIDDEN;
+@@ -944,6 +1048,17 @@ auth_checker(request_rec *r)
+   return OK;
+ }
+ 
++#if USE_FORCE_AUTHN
++static int
++force_authn(request_rec *r)
++{
++  if (apr_table_get(r->notes, FORCE_AUTHN_NOTE))
++    return OK;
++
++  return DECLINED;
++}
++#endif
++
+ #if DAV_SVN_LOAD_OPTIONAL
+ #undef dav_svn_get_repos_path
+ #undef dav_svn_split_uri
+@@ -970,6 +1085,9 @@ register_hooks(apr_pool_t *p)
+    * give SSLOptions +FakeBasicAuth a chance to work. */
+   ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST);
+   ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST);
++#if USE_FORCE_AUTHN
++  ap_hook_force_authn(force_authn, NULL, NULL, APR_HOOK_FIRST);
++#endif
+   ap_register_provider(p,
+                        AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP,
+                        AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME,
+Index: subversion/tests/cmdline/README
+===================================================================
+--- a/subversion/tests/cmdline/README	(revision 1691883)
++++ b/subversion/tests/cmdline/README	(working copy)
+@@ -83,6 +83,133 @@ paths adjusted appropriately:
+      Require valid-user
+    </Location>
+ 
++   <Location /authz-test-work/anon>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     # This may seem unnecessary but granting access to everyone here is necessary
++     # to exercise a bug with httpd 2.3.x+.  The "Require all granted" syntax is
++     # new to 2.3.x+ which we can detect with the mod_authz_core.c module
++     # signature.  Use the "Allow from all" syntax with older versions for symmetry.
++     <IfModule mod_authz_core.c>
++       Require all granted
++     </IfModule>
++     <IfModule !mod_authz_core.c>
++       Allow from all
++     </IfMOdule>
++   </Location>
++   <Location /authz-test-work/mixed>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     Require           valid-user
++     Satisfy Any
++   </Location>
++   <Location /authz-test-work/mixed-noauthwhenanon>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     Require           valid-user
++     AuthzSVNNoAuthWhenAnonymousAllowed On
++   </Location>
++   <Location /authz-test-work/authn>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     Require           valid-user
++   </Location>
++   <Location /authz-test-work/authn-anonoff>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     Require           valid-user
++     AuthzSVNAnonymous Off
++   </Location>
++   <Location /authz-test-work/authn-lcuser>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     Require           valid-user
++     AuthzForceUsernameCase Lower
++   </Location>
++   <Location /authz-test-work/authn-lcuser>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     Require           valid-user
++     AuthzForceUsernameCase Lower
++   </Location>
++   <Location /authz-test-work/authn-group>
++     DAV               svn
++     SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++     AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++     SVNListParentPath On
++     AuthType          Basic
++     AuthName          "Subversion Repository"
++     AuthUserFile /usr/local/apache2/conf/users
++     AuthGroupFile /usr/local/apache2/conf/groups
++     Require           group random
++     AuthzSVNAuthoritative Off
++   </Location>
++   <IfModule mod_authz_core.c>
++     <Location /authz-test-work/sallrany>
++       DAV               svn
++       SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++       AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++       SVNListParentPath On
++       AuthType          Basic
++       AuthName          "Subversion Repository"
++       AuthUserFile /usr/local/apache2/conf/users
++       AuthzSendForbiddenOnFailure On
++       Satisfy All
++       <RequireAny>
++         Require valid-user
++         Require expr req('ALLOW') == '1'
++       </RequireAny>
++     </Location>
++     <Location /authz-test-work/sallrall>
++       DAV               svn
++       SVNParentPath /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/local_tmp
++       AuthzSVNAccessFile /home/yourusernamehere/projects/svn/subversion/tests/cmdline/svn-test-work/authz
++       SVNListParentPath On
++       AuthType          Basic
++       AuthName          "Subversion Repository"
++       AuthUserFile /usr/local/apache2/conf/users
++       AuthzSendForbiddenOnFailure On
++       Satisfy All
++       <RequireAll>
++         Require valid-user
++         Require expr req('ALLOW') == '1'
++       </RequireAll>
++     </Location>
++   </IfModule>
++
++
+    RedirectMatch permanent ^/svn-test-work/repositories/REDIRECT-PERM-(.*)$ /svn-test-work/repositories/$1
+    RedirectMatch           ^/svn-test-work/repositories/REDIRECT-TEMP-(.*)$ /svn-test-work/repositories/$1
+ 
+@@ -101,8 +228,17 @@ just drop the following 2-line snippet into the
+ ----------------------------
+ jrandom:xCGl35kV9oWCY
+ jconstant:xCGl35kV9oWCY
++JRANDOM:xCGl35kV9oWCY
++JCONSTANT:xCGl35kV9oWCY
+ ----------------------------
+ 
++and these lines into the
++/usr/local/apache/conf/groups file:
++----------------------------
++random: jrandom
++constant: jconstant
++----------------------------
++
+ Now, (re)start Apache and run the tests over mod_dav_svn.
+ 
+ You can run a test script over DAV:
+@@ -138,6 +274,8 @@ Note [1]: It would be quite too much to expect tho
+           ----------------------------
+           jrandom:$apr1$3p1.....$FQW6RceW5QhJ2blWDQgKn0
+           jconstant:$apr1$jp1.....$Usrqji1c9H6AbOxOGAzzb0
++          JRANDOM:$apr1$3p1.....$FQW6RceW5QhJ2blWDQgKn0
++          JCONSTANT:$apr1$jp1.....$Usrqji1c9H6AbOxOGAzzb0
+           ----------------------------
+ 
+ 
+Index: subversion/tests/cmdline/davautocheck.sh
+===================================================================
+--- a/subversion/tests/cmdline/davautocheck.sh	(revision 1691883)
++++ b/subversion/tests/cmdline/davautocheck.sh	(working copy)
+@@ -289,8 +289,6 @@ LOAD_MOD_AUTHN_CORE="$(get_loadmodule_config mod_a
+     || fail "Authn_Core module not found."
+ LOAD_MOD_AUTHZ_CORE="$(get_loadmodule_config mod_authz_core)" \
+     || fail "Authz_Core module not found."
+-LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \
+-    || fail "Authz_Host module not found."
+ LOAD_MOD_UNIXD=$(get_loadmodule_config mod_unixd) \
+     || fail "UnixD module not found"
+ }
+@@ -298,6 +296,10 @@ LOAD_MOD_AUTHN_FILE="$(get_loadmodule_config mod_a
+     || fail "Authn_File module not found."
+ LOAD_MOD_AUTHZ_USER="$(get_loadmodule_config mod_authz_user)" \
+     || fail "Authz_User module not found."
++LOAD_MOD_AUTHZ_GROUPFILE="$(get_loadmodule_config mod_authz_groupfile)" \
++    || fail "Authz_GroupFile module not found."
++LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \
++    || fail "Authz_Host module not found."
+ }
+ if [ ${APACHE_MPM:+set} ]; then
+     LOAD_MOD_MPM=$(get_loadmodule_config mod_mpm_$APACHE_MPM) \
+@@ -328,6 +330,7 @@ HTTPD_ERROR_LOG="$HTTPD_ROOT/error_log"
+ HTTPD_MIME_TYPES="$HTTPD_ROOT/mime.types"
+ BASE_URL="http://localhost:$HTTPD_PORT";
+ HTTPD_USERS="$HTTPD_ROOT/users"
++HTTPD_GROUPS="$HTTPD_ROOT/groups"
+ 
+ mkdir "$HTTPD_ROOT" \
+   || fail "couldn't create temporary directory '$HTTPD_ROOT'"
+@@ -388,6 +391,14 @@ fi
+ say "Adding users for lock authentication"
+ $HTPASSWD -bc $HTTPD_USERS jrandom   rayjandom
+ $HTPASSWD -b  $HTTPD_USERS jconstant rayjandom
++$HTPASSWD -b  $HTTPD_USERS JRANDOM   rayjandom
++$HTPASSWD -b  $HTTPD_USERS JCONSTANT rayjandom
++ 
++say "Adding groups for mod_authz_svn tests"
++cat > "$HTTPD_GROUPS" <<__EOF__
++random: jrandom
++constant: jconstant
++__EOF__
+ 
+ touch $HTTPD_MIME_TYPES
+ 
+@@ -405,7 +416,9 @@ $LOAD_MOD_AUTHN_CORE
+ $LOAD_MOD_AUTHN_FILE
+ $LOAD_MOD_AUTHZ_CORE
+ $LOAD_MOD_AUTHZ_USER
++$LOAD_MOD_AUTHZ_GROUPFILE
+ $LOAD_MOD_AUTHZ_HOST
++$LOAD_MOD_ACCESS_COMPAT
+ LoadModule          authz_svn_module "$MOD_AUTHZ_SVN"
+ 
+ __EOF__
+@@ -497,6 +510,161 @@ CustomLog           "$HTTPD_ROOT/ops" "%t %u %{SVN
+   SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
+   ${SVN_PATH_AUTHZ_LINE}
+ </Location>
++<Location /authz-test-work/anon>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  # This may seem unnecessary but granting access to everyone here is necessary
++  # to exercise a bug with httpd 2.3.x+.  The "Require all granted" syntax is
++  # new to 2.3.x+ which we can detect with the mod_authz_core.c module
++  # signature.  Use the "Allow from all" syntax with older versions for symmetry.
++  <IfModule mod_authz_core.c>
++    Require all granted
++  </IfModule>
++  <IfModule !mod_authz_core.c>
++    Allow from all
++  </IfMOdule>
++  ${SVN_PATH_AUTHZ_LINE}
++</Location>
++<Location /authz-test-work/mixed>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  Require           valid-user
++  Satisfy Any
++  ${SVN_PATH_AUTHZ_LINE}
++</Location>
++<Location /authz-test-work/mixed-noauthwhenanon>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  Require           valid-user
++  AuthzSVNNoAuthWhenAnonymousAllowed On
++  SVNPathAuthz On
++</Location>
++<Location /authz-test-work/authn>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  Require           valid-user
++  ${SVN_PATH_AUTHZ_LINE}
++</Location>
++<Location /authz-test-work/authn-anonoff>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  Require           valid-user
++  AuthzSVNAnonymous Off
++  SVNPathAuthz On
++</Location>
++<Location /authz-test-work/authn-lcuser>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  Require           valid-user
++  AuthzForceUsernameCase Lower
++  ${SVN_PATH_AUTHZ_LINE}
++</Location>
++<Location /authz-test-work/authn-lcuser>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  Require           valid-user
++  AuthzForceUsernameCase Lower
++  ${SVN_PATH_AUTHZ_LINE}
++</Location>
++<Location /authz-test-work/authn-group>
++  DAV               svn
++  SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++  AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++  SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++  SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++  SVNListParentPath On
++  AuthType          Basic
++  AuthName          "Subversion Repository"
++  AuthUserFile      $HTTPD_USERS
++  AuthGroupFile     $HTTPD_GROUPS
++  Require           group random
++  AuthzSVNAuthoritative Off
++  SVNPathAuthz On
++</Location>
++<IfModule mod_authz_core.c>
++  <Location /authz-test-work/sallrany>
++    DAV               svn
++    SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++    AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++    SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++    SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++    SVNListParentPath On
++    AuthType          Basic
++    AuthName          "Subversion Repository"
++    AuthUserFile      $HTTPD_USERS
++    AuthzSendForbiddenOnFailure On
++    Satisfy All
++    <RequireAny>
++      Require valid-user
++      Require expr req('ALLOW') == '1'
++    </RequireAny>
++    ${SVN_PATH_AUTHZ_LINE}
++  </Location>
++  <Location /authz-test-work/sallrall>
++    DAV               svn
++    SVNParentPath     "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/local_tmp"
++    AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz"
++    SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL}
++    SVNCacheRevProps  ${CACHE_REVPROPS_SETTING}
++    SVNListParentPath On
++    AuthType          Basic
++    AuthName          "Subversion Repository"
++    AuthUserFile      $HTTPD_USERS
++    AuthzSendForbiddenOnFailure On
++    Satisfy All
++    <RequireAll>
++      Require valid-user
++      Require expr req('ALLOW') == '1'
++    </RequireAll>
++    ${SVN_PATH_AUTHZ_LINE}
++  </Location>
++</IfModule>
+ RedirectMatch permanent ^/svn-test-work/repositories/REDIRECT-PERM-(.*)\$ /svn-test-work/repositories/\$1
+ RedirectMatch           ^/svn-test-work/repositories/REDIRECT-TEMP-(.*)\$ /svn-test-work/repositories/\$1
+ __EOF__
+Index: subversion/tests/cmdline/mod_authz_svn_tests.py
+===================================================================
+--- a/subversion/tests/cmdline/mod_authz_svn_tests.py	(nonexistent)
++++ b/subversion/tests/cmdline/mod_authz_svn_tests.py	(working copy)
+@@ -0,0 +1,1073 @@
++#!/usr/bin/env python
++#
++#  mod_authz_svn_tests.py:  testing mod_authz_svn
++#
++#  Subversion is a tool for revision control.
++#  See http://subversion.apache.org for more information.
++#
++# ====================================================================
++#    Licensed to the Apache Software Foundation (ASF) under one
++#    or more contributor license agreements.  See the NOTICE file
++#    distributed with this work for additional information
++#    regarding copyright ownership.  The ASF licenses this file
++#    to you under the Apache License, Version 2.0 (the
++#    "License"); you may not use this file except in compliance
++#    with the License.  You may obtain a copy of the License at
++#
++#      http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing,
++#    software distributed under the License is distributed on an
++#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++#    KIND, either express or implied.  See the License for the
++#    specific language governing permissions and limitations
++#    under the License.
++######################################################################
++
++# General modules
++import os, re, logging
++
++logger = logging.getLogger()
++
++# Our testing module
++import svntest
++
++# (abbreviation)
++Skip = svntest.testcase.Skip_deco
++SkipUnless = svntest.testcase.SkipUnless_deco
++XFail = svntest.testcase.XFail_deco
++Issues = svntest.testcase.Issues_deco
++Issue = svntest.testcase.Issue_deco
++Wimp = svntest.testcase.Wimp_deco
++
++ls_of_D_no_H = '''<html><head><title>repos - Revision 1: /A/D</title></head>
++<body>
++ <h2>repos - Revision 1: /A/D</h2>
++ <ul>
++  <li><a href="../">..</a></li>
++  <li><a href="G/">G/</a></li>
++  <li><a href="gamma">gamma</a></li>
++ </ul>
++</body></html>'''
++
++ls_of_D_H = '''<html><head><title>repos - Revision 1: /A/D</title></head>
++<body>
++ <h2>repos - Revision 1: /A/D</h2>
++ <ul>
++  <li><a href="../">..</a></li>
++  <li><a href="G/">G/</a></li>
++  <li><a href="H/">H/</a></li>
++  <li><a href="gamma">gamma</a></li>
++ </ul>
++</body></html>'''
++
++ls_of_H = '''<html><head><title>repos - Revision 1: /A/D/H</title></head>
++<body>
++ <h2>repos - Revision 1: /A/D/H</h2>
++ <ul>
++  <li><a href="../">..</a></li>
++  <li><a href="chi">chi</a></li>
++  <li><a href="omega">omega</a></li>
++  <li><a href="psi">psi</a></li>
++ </ul>
++</body></html>'''
++
++user1 = svntest.main.wc_author
++user1_upper = user1.upper()
++user1_pass = svntest.main.wc_passwd
++user1_badpass = 'XXX'
++assert user1_pass != user1_badpass, "Passwords can't match"
++user2 = svntest.main.wc_author2
++user2_upper = user2.upper()
++user2_pass = svntest.main.wc_passwd
++user2_badpass = 'XXX'
++assert user2_pass != user2_badpass, "Passwords can't match"
++
++def write_authz_file(sbox):
++    svntest.main.write_authz_file(sbox, {
++                                          '/':  '$anonymous = r\n' +
++                                                'jrandom = rw\n' +
++                                                'jconstant = rw',
++                                          '/A/D/H': '$anonymous =\n' +
++                                                    '$authenticated =\n' +
++                                                    'jrandom = rw'
++                                        })
++
++def write_authz_file_groups(sbox):
++    authz_name = sbox.authz_name()
++    svntest.main.write_authz_file(sbox,{
++                                         '/':  '* =',
++                                       })
++
++def verify_get(test_area_url, path, user, pw,
++               expected_status, expected_body, headers):
++  import httplib
++  from urlparse import urlparse
++  import base64
++
++  req_url = test_area_url + path
++
++  loc = urlparse(req_url)
++
++  if loc.scheme == 'http':
++    h = httplib.HTTPConnection(loc.hostname, loc.port)
++  else:
++    h = httplib.HTTPSConnection(loc.hostname, loc.port)
++
++  if headers is None:
++    headers = {}
++
++  if user and pw:
++      auth_info = user + ':' + pw
++      headers['Authorization'] = 'Basic ' + base64.b64encode(auth_info)
++  else:
++      auth_info = "anonymous"
++
++  h.request('GET', req_url, None, headers)
++
++  r = h.getresponse()
++
++  actual_status = r.status
++  if expected_status and expected_status != actual_status:
++
++      logger.warn("Expected status '" + str(expected_status) +
++                  "' but got '" + str(actual_status) +
++                  "' on url '" + req_url + "' (" +
++                  auth_info + ").")
++      raise svntest.Failure
++
++  if expected_body:
++      actual_body = r.read()
++      if expected_body != actual_body:
++        logger.warn("Expected body:")
++        logger.warn(expected_body)
++        logger.warn("But got:")
++        logger.warn(actual_body)
++        logger.warn("on url '" + req_url + "' (" + auth_info + ").")
++        raise svntest.Failure
++
++def verify_gets(test_area_url, tests):
++  for test in tests:
++      verify_get(test_area_url, test['path'], test.get('user'), test.get('pw'),
++                 test['status'], test.get('body'), test.get('headers'))
++
++
++######################################################################
++# Tests
++#
++#   Each test must return on success or raise on failure.
++
++
++#----------------------------------------------------------------------
++
++
++@SkipUnless(svntest.main.is_ra_type_dav)
++def anon(sbox):
++  "test anonymous access"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/anon')
++
++  write_authz_file(sbox)
++
++  anon_tests = ( 
++                 { 'path': '', 'status': 301 },
++                 { 'path': '/', 'status': 200 },
++                 { 'path': '/repos', 'status': 301 },
++                 { 'path': '/repos/', 'status': 200 },
++                 { 'path': '/repos/A', 'status': 301 },
++                 { 'path': '/repos/A/', 'status': 200 },
++                 { 'path': '/repos/A/D', 'status': 301 },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H },
++                 { 'path': '/repos/A/D/gamma', 'status': 200 },
++                 { 'path': '/repos/A/D/H', 'status': 403 },
++                 { 'path': '/repos/A/D/H/', 'status': 403 },
++                 { 'path': '/repos/A/D/H/chi', 'status': 403 },
++                 # auth isn't configured so nothing should change when passing
++                 # authn details
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_badpass},
++               )
++
++  verify_gets(test_area_url, anon_tests)
++
++
++@SkipUnless(svntest.main.is_ra_type_dav)
++def mixed(sbox):
++  "test mixed anonymous and authenticated access"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/mixed')
++
++  write_authz_file(sbox)
++
++  mixed_tests = (
++                 { 'path': '', 'status': 301,  },
++                 { 'path': '/', 'status': 200,  },
++                 { 'path': '/repos', 'status': 301,  },
++                 { 'path': '/repos/', 'status': 200,  },
++                 { 'path': '/repos/A', 'status': 301,  },
++                 { 'path': '/repos/A/', 'status': 200,  },
++                 { 'path': '/repos/A/D', 'status': 301,  },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, },
++                 { 'path': '/repos/A/D/H', 'status': 401, },
++                 { 'path': '/repos/A/D/H/', 'status': 401, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, },
++                 # auth is configured and user1 is allowed access to H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 )
++
++  verify_gets(test_area_url, mixed_tests)
++
++@SkipUnless(svntest.main.is_ra_type_dav)
++@XFail(svntest.main.is_httpd_authz_provider_enabled)
++# uses the AuthzSVNNoAuthWhenAnonymousAllowed On directive
++# this is broken with httpd 2.3.x+ since it requires the auth system to accept
++# r->user == NULL and there is a test for this in server/request.c now.  It
++# was intended as a workaround for the lack of Satisfy Any in 2.3.x+ which
++# was resolved by httpd with mod_access_compat in 2.3.x+.
++def mixed_noauthwhenanon(sbox):
++  "test mixed with noauthwhenanon directive"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/mixed-noauthwhenanon')
++
++  write_authz_file(sbox)
++
++  noauthwhenanon_tests = (
++                 { 'path': '', 'status': 301,  },
++                 { 'path': '/', 'status': 200,  },
++                 { 'path': '/repos', 'status': 301,  },
++                 { 'path': '/repos/', 'status': 200,  },
++                 { 'path': '/repos/A', 'status': 301,  },
++                 { 'path': '/repos/A/', 'status': 200,  },
++                 { 'path': '/repos/A/D', 'status': 301,  },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, },
++                 { 'path': '/repos/A/D/H', 'status': 401, },
++                 { 'path': '/repos/A/D/H/', 'status': 401, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, },
++                 # auth is configured and user1 is allowed access to H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 # try with the wrong password for user1
++                 # note that unlike doing this with Satisfy Any this case
++                 # actually provides anon access when provided with an invalid
++                 # password
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 )
++
++  verify_gets(test_area_url, noauthwhenanon_tests)
++
++
++@SkipUnless(svntest.main.is_ra_type_dav)
++def authn(sbox):
++  "test authenticated only access"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/authn')
++
++  write_authz_file(sbox)
++
++  authn_tests = (
++                 { 'path': '', 'status': 401,  },
++                 { 'path': '/', 'status': 401,  },
++                 { 'path': '/repos', 'status': 401,  },
++                 { 'path': '/repos/', 'status': 401,  },
++                 { 'path': '/repos/A', 'status': 401,  },
++                 { 'path': '/repos/A/', 'status': 401,  },
++                 { 'path': '/repos/A/D', 'status': 401,  },
++                 { 'path': '/repos/A/D/', 'status': 401, },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, },
++                 { 'path': '/repos/A/D/H', 'status': 401, },
++                 { 'path': '/repos/A/D/H/', 'status': 401, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, },
++                 # auth is configured and user1 is allowed access to H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 # try with upper case username for user1
++                 { 'path': '', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 # try with upper case username for user2
++                 { 'path': '', 'status': 301, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 )
++
++  verify_gets(test_area_url, authn_tests)
++
++@SkipUnless(svntest.main.is_ra_type_dav)
++def authn_anonoff(sbox):
++  "test authenticated only access with anonoff"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/authn-anonoff')
++
++  write_authz_file(sbox)
++
++  anonoff_tests = (
++                 { 'path': '', 'status': 401,  },
++                 { 'path': '/', 'status': 401,  },
++                 { 'path': '/repos', 'status': 401,  },
++                 { 'path': '/repos/', 'status': 401,  },
++                 { 'path': '/repos/A', 'status': 401,  },
++                 { 'path': '/repos/A/', 'status': 401,  },
++                 { 'path': '/repos/A/D', 'status': 401,  },
++                 { 'path': '/repos/A/D/', 'status': 401, },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, },
++                 { 'path': '/repos/A/D/H', 'status': 401, },
++                 { 'path': '/repos/A/D/H/', 'status': 401, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, },
++                 # auth is configured and user1 is allowed access to H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 # try with upper case username for user1
++                 { 'path': '', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user1_upper, 'pw': user1_pass},
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 # try with upper case username for user2
++                 { 'path': '', 'status': 301, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 )
++
++  verify_gets(test_area_url, anonoff_tests)
++
++@SkipUnless(svntest.main.is_ra_type_dav)
++def authn_lcuser(sbox):
++  "test authenticated only access with lcuser"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/authn-lcuser')
++
++  write_authz_file(sbox)
++
++  lcuser_tests = (
++                 # try with upper case username for user1 (works due to lcuser option)
++                 { 'path': '', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1_upper, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1_upper, 'pw': user1_pass},
++                 # try with upper case username for user2 (works due to lcuser option)
++                 { 'path': '', 'status': 301, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2_upper, 'pw': user2_pass},
++                 )
++
++  verify_gets(test_area_url, lcuser_tests)
++
++# authenticated access only by group - a excuse to use AuthzSVNAuthoritative Off
++# this is terribly messed up, Require group runs after mod_authz_svn.
++# so if mod_authz_svn grants the access then it doesn't matter what the group
++# requirement says.  If we reject the access then you can use the AuthzSVNAuthoritative Off
++# directive to fall through to the group check.  Overall the behavior of setups like this
++# is almost guaranteed to not be what users expect.
++@SkipUnless(svntest.main.is_ra_type_dav)
++def authn_group(sbox):
++  "test authenticated only access via groups"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/authn-group')
++
++  # Can't use write_authz_file() as most tests because we want to deny all
++  # access with mod_authz_svn so the tests fall through to the group handling
++  authz_name = sbox.authz_name()
++  svntest.main.write_authz_file(sbox, {
++                                        '/':  '* =',
++                                      })
++
++  group_tests = (
++                 { 'path': '', 'status': 401, },
++                 { 'path': '/', 'status': 401, },
++                 { 'path': '/repos', 'status': 401, },
++                 { 'path': '/repos/', 'status': 401, },
++                 { 'path': '/repos/A', 'status': 401, },
++                 { 'path': '/repos/A/', 'status': 401, },
++                 { 'path': '/repos/A/D', 'status': 401, },
++                 { 'path': '/repos/A/D/', 'status': 401, },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, },
++                 { 'path': '/repos/A/D/H', 'status': 401, },
++                 { 'path': '/repos/A/D/H/', 'status': 401, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, },
++                 # auth is configured and user1 is allowed access repo including H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 )
++
++  verify_gets(test_area_url, group_tests)
++
++# This test exists to validate our behavior when used with the new authz
++# provider system introduced in httpd 2.3.x.  The Satisfy directive
++# determines how older authz hooks are combined and the RequireA(ll|ny)
++# blocks handles how new authz providers are combined.  The overall results of
++# all the authz providers (combined per the Require* blocks) are then
++# combined with the other authz hooks via the Satisfy directive.
++# Meaning this test requires that mod_authz_svn says yes and there is
++# either a valid user or the ALLOW header is 1.  The header may seem
++# like a silly test but it's easier to excercise than say a host directive
++# in a repeatable test.
++@SkipUnless(svntest.main.is_httpd_authz_provider_enabled)
++def authn_sallrany(sbox):
++  "test satisfy all require any config"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/sallrany')
++
++  write_authz_file(sbox)
++
++  allow_header = { 'ALLOW': '1' }
++
++  sallrany_tests = (
++                 #anon access isn't allowed without ALLOW header
++                 { 'path': '', 'status': 401, },
++                 { 'path': '/', 'status': 401, },
++                 { 'path': '/repos', 'status': 401, },
++                 { 'path': '/repos/', 'status': 401, },
++                 { 'path': '/repos/A', 'status': 401, },
++                 { 'path': '/repos/A/', 'status': 401, },
++                 { 'path': '/repos/A/D', 'status': 401, },
++                 { 'path': '/repos/A/D/', 'status': 401, },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, },
++                 { 'path': '/repos/A/D/H', 'status': 401, },
++                 { 'path': '/repos/A/D/H/', 'status': 401, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, },
++                 # auth is configured and user1 is allowed access repo including H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass},
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass},
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass},
++                 # anon is allowed with the ALLOW header
++                 { 'path': '', 'status': 301, 'headers': allow_header },
++                 { 'path': '/', 'status': 200, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 301, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 200, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 301, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 200, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 301, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'headers': allow_header },
++                 # these 3 tests return 403 instead of 401 becasue the config allows
++                 # the anon user with the ALLOW header without any auth and the old hook
++                 # system has no way of knowing it should return 401 since authentication is
++                 # configured and can change the behavior.  It could decide to return 401 just on
++                 # the basis of authentication being configured but then that leaks info in other
++                 # cases so it's better for this case to be "broken".
++                 { 'path': '/repos/A/D/H', 'status': 403, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'headers': allow_header },
++                 # auth is configured and user1 is allowed access repo including H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++
++                 )
++
++  verify_gets(test_area_url, sallrany_tests)
++
++# See comments on authn_sallrany test for some background on the interaction
++# of Satisfy Any and the newer Require blocks.
++@SkipUnless(svntest.main.is_httpd_authz_provider_enabled)
++def authn_sallrall(sbox):
++  "test satisfy all require all config"
++  sbox.build(read_only = True, create_wc = False)
++
++  test_area_url = sbox.repo_url.replace('/svn-test-work/local_tmp/repos',
++                                        '/authz-test-work/sallrall')
++
++  write_authz_file(sbox)
++
++  allow_header = { 'ALLOW': '1' }
++
++  sallrall_tests = (
++                 #anon access isn't allowed without ALLOW header
++                 { 'path': '', 'status': 403, },
++                 { 'path': '/', 'status': 403, },
++                 { 'path': '/repos', 'status': 403, },
++                 { 'path': '/repos/', 'status': 403, },
++                 { 'path': '/repos/A', 'status': 403, },
++                 { 'path': '/repos/A/', 'status': 403, },
++                 { 'path': '/repos/A/D', 'status': 403, },
++                 { 'path': '/repos/A/D/', 'status': 403, },
++                 { 'path': '/repos/A/D/gamma', 'status': 403, },
++                 { 'path': '/repos/A/D/H', 'status': 403, },
++                 { 'path': '/repos/A/D/H/', 'status': 403, },
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, },
++                 # auth is configured but no access is allowed without the ALLOW header
++                 { 'path': '', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user1, 'pw': user1_pass},
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user1, 'pw': user1_badpass},
++                 # auth is configured but no access is allowed without the ALLOW header
++                 { 'path': '', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass},
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/gamma', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_badpass},
++                 # anon is not allowed even with ALLOW header
++                 { 'path': '', 'status': 401, 'headers': allow_header },
++                 { 'path': '/', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'headers': allow_header },
++                 # auth is configured and user1 is allowed access repo including H
++                 { 'path': '', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_H,
++                   'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 301, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 200, 'body': ls_of_H, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 200, 'user': user1, 'pw': user1_pass, 'headers': allow_header },
++                 # try with the wrong password for user1
++                 { 'path': '', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user1, 'pw': user1_badpass, 'headers': allow_header },
++                 # auth is configured and user2 is not allowed access to H
++                 { 'path': '', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 301, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 200, 'body': ls_of_D_no_H,
++                   'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 200, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 403, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 403, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 403, 'user': user2, 'pw': user2_pass, 'headers': allow_header },
++                 # try with the wrong password for user2
++                 { 'path': '', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/gamma', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++                 { 'path': '/repos/A/D/H/chi', 'status': 401, 'user': user2, 'pw': user2_badpass, 'headers': allow_header },
++
++                 )
++
++  verify_gets(test_area_url, sallrall_tests)
++
++
++########################################################################
++# Run the tests
++
++
++# list all tests here, starting with None:
++test_list = [ None,
++              anon,
++              mixed,
++              mixed_noauthwhenanon,
++              authn,
++              authn_anonoff,
++              authn_lcuser,
++              authn_group,
++              authn_sallrany,
++              authn_sallrall,
++             ]
++serial_only = True
++
++if __name__ == '__main__':
++  svntest.main.run_tests(test_list)
++  # NOTREACHED
++
++
++### End of file.
+
+Property changes on: subversion/tests/cmdline/mod_authz_svn_tests.py
+___________________________________________________________________
+Added: svn:eol-style
+## -0,0 +1 ##
++native
+\ No newline at end of property
+Index: subversion/tests/cmdline/svntest/main.py
+===================================================================
+--- a/subversion/tests/cmdline/svntest/main.py	(revision 1691883)
++++ b/subversion/tests/cmdline/svntest/main.py	(working copy)
+@@ -1378,6 +1378,30 @@ def is_plaintext_password_storage_disabled():
+     return False
+   return True
+ 
++
++# https://issues.apache.org/bugzilla/show_bug.cgi?id=56480
++# https://issues.apache.org/bugzilla/show_bug.cgi?id=55397
++__mod_dav_url_quoting_broken_versions = frozenset([
++    '2.2.27',
++    '2.2.26',
++    '2.2.25',
++    '2.4.9',
++    '2.4.8',
++    '2.4.7',
++    '2.4.6',
++    '2.4.5',
++])
++def is_mod_dav_url_quoting_broken():
++    if is_ra_type_dav():
++        return (options.httpd_version in __mod_dav_url_quoting_broken_versions)
++    return None
++
++def is_httpd_authz_provider_enabled():
++    if is_ra_type_dav():
++      v = options.httpd_version.split('.')
++      return (v[0] == '2' and int(v[1]) >= 3) or int(v[0]) > 2
++    return None
++
+ ######################################################################
+ 
+ 
+@@ -1435,6 +1459,8 @@ class TestSpawningThread(threading.Thread):
+       args.append('--ssl-cert=' + options.ssl_cert)
+     if options.http_proxy:
+       args.append('--http-proxy=' + options.http_proxy)
++    if options.httpd_version:
++      args.append('--httpd-version=' + options.httpd_version)
+ 
+     result, stdout_lines, stderr_lines = spawn_process(command, 0, False, None,
+                                                        *args)
+@@ -1600,6 +1626,12 @@ class TestRunner:
+       sandbox.cleanup_test_paths()
+     return exit_code
+ 
++def is_httpd_authz_provider_enabled():
++    if is_ra_type_dav():
++      v = options.httpd_version.split('.')
++      return (v[0] == '2' and int(v[1]) >= 3) or int(v[0]) > 2
++    return None
++
+ ######################################################################
+ # Main testing functions
+ 
+@@ -1780,6 +1812,8 @@ def _create_parser():
+                     help='Path to SSL server certificate.')
+   parser.add_option('--http-proxy', action='store',
+                     help='Use the HTTP Proxy at hostname:port.')
++  parser.add_option('--httpd-version', action='store',
++                    help='Assume HTTPD is this version.')
+   parser.add_option('--tools-bin', action='store', dest='tools_bin',
+                     help='Use the svn tools installed in this path')
+ 
+Index: win-tests.py
+===================================================================
+--- a/win-tests.py	(revision 1691883)
++++ b/win-tests.py	(working copy)
+@@ -481,6 +481,7 @@ class Httpd:
+     self.httpd_config = os.path.join(self.root, 'httpd.conf')
+     self.httpd_users = os.path.join(self.root, 'users')
+     self.httpd_mime_types = os.path.join(self.root, 'mime.types')
++    self.httpd_groups = os.path.join(self.root, 'groups')
+     self.abs_builddir = abs_builddir
+     self.abs_objdir = abs_objdir
+     self.service_name = 'svn-test-httpd-' + str(httpd_port)
+@@ -494,6 +495,7 @@ class Httpd:
+     create_target_dir(self.root_dir)
+ 
+     self._create_users_file()
++    self._create_groups_file()
+     self._create_mime_types_file()
+     self._create_dontdothat_file()
+ 
+@@ -540,6 +542,8 @@ class Httpd:
+     if self.httpd_ver >= 2.2:
+       fp.write(self._sys_module('auth_basic_module', 'mod_auth_basic.so'))
+       fp.write(self._sys_module('authn_file_module', 'mod_authn_file.so'))
++      fp.write(self._sys_module('authz_groupfile_module', 'mod_authz_groupfile.so'))
++      fp.write(self._sys_module('authz_host_module', 'mod_authz_host.so'))
+     else:
+       fp.write(self._sys_module('auth_module', 'mod_auth.so'))
+     fp.write(self._sys_module('alias_module', 'mod_alias.so'))
+@@ -562,6 +566,7 @@ class Httpd:
+     # Define two locations for repositories
+     fp.write(self._svn_repo('repositories'))
+     fp.write(self._svn_repo('local_tmp'))
++    fp.write(self._svn_authz_repo())
+ 
+     # And two redirects for the redirect tests
+     fp.write('RedirectMatch permanent ^/svn-test-work/repositories/'
+@@ -592,7 +597,18 @@ class Httpd:
+                                     'jrandom', 'rayjandom'])
+     os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
+                                     'jconstant', 'rayjandom'])
++    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
++                                    'JRANDOM', 'rayjandom'])
++    os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp',  self.httpd_users,
++                                    'JCONSTANT', 'rayjandom'])
+ 
++  def _create_groups_file(self):
++    "Create groups for mod_authz_svn tests"
++    fp = open(self.httpd_groups, 'w')
++    fp.write('random: jrandom\n')
++    fp.write('constant: jconstant\n')
++    fp.close()
++
+   def _create_mime_types_file(self):
+     "Create empty mime.types file"
+     fp = open(self.httpd_mime_types, 'w')
+@@ -652,6 +668,153 @@ class Httpd:
+       '  DontDoThatConfigFile ' + self._quote(self.dontdothat_file) + '\n' \
+       '</Location>\n'
+ 
++  def _svn_authz_repo(self):
++    local_tmp = os.path.join(self.abs_builddir,
++                             CMDLINE_TEST_SCRIPT_NATIVE_PATH,
++                             'svn-test-work', 'local_tmp')
++    return \
++      '<Location /authz-test-work/anon>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  <IfModule mod_authz_core.c>' + '\n' \
++      '    Require all granted' + '\n' \
++      '  </IfModule>' + '\n' \
++      '  <IfModule !mod_authz_core.c>' + '\n' \
++      '    Allow from all' + '\n' \
++      '  </IfModule>' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/mixed>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  Require           valid-user' + '\n' \
++      '  Satisfy Any' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/mixed-noauthwhenanon>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  Require           valid-user' + '\n' \
++      '  AuthzSVNNoAuthWhenAnonymousAllowed On' + '\n' \
++      '  SVNPathAuthz On' + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/authn>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  Require           valid-user' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/authn-anonoff>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  Require           valid-user' + '\n' \
++      '  AuthzSVNAnonymous Off' + '\n' \
++      '  SVNPathAuthz On' + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/authn-lcuser>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  Require           valid-user' + '\n' \
++      '  AuthzForceUsernameCase Lower' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/authn-lcuser>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  Require           valid-user' + '\n' \
++      '  AuthzForceUsernameCase Lower' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/authn-group>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  AuthGroupFile    ' + self._quote(self.httpd_groups) + '\n' \
++      '  Require           group random' + '\n' \
++      '  AuthzSVNAuthoritative Off' + '\n' \
++      '  SVNPathAuthz On' + '\n' \
++      '</Location>' + '\n' \
++      '<IfModule mod_authz_core.c>' + '\n' \
++      '<Location /authz-test-work/sallrany>' + '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  AuthzSendForbiddenOnFailure On' + '\n' \
++      '  Satisfy All' + '\n' \
++      '  <RequireAny>' + '\n' \
++      '    Require valid-user' + '\n' \
++      '    Require expr req(\'ALLOW\') == \'1\'' + '\n' \
++      '  </RequireAny>' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '<Location /authz-test-work/sallrall>'+ '\n' \
++      '  DAV               svn' + '\n' \
++      '  SVNParentPath     ' + local_tmp + '\n' \
++      '  AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
++      '  SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
++      '  SVNListParentPath On' + '\n' \
++      '  AuthType          Basic' + '\n' \
++      '  AuthName          "Subversion Repository"' + '\n' \
++      '  AuthUserFile    ' + self._quote(self.httpd_users) + '\n' \
++      '  AuthzSendForbiddenOnFailure On' + '\n' \
++      '  Satisfy All' + '\n' \
++      '  <RequireAll>' + '\n' \
++      '    Require valid-user' + '\n' \
++      '    Require expr req(\'ALLOW\') == \'1\'' + '\n' \
++      '  </RequireAll>' + '\n' \
++      '  SVNPathAuthz ' + self.path_authz_option + '\n' \
++      '</Location>' + '\n' \
++      '</IfModule>' + '\n' \
++
+   def start(self):
+     if self.service:
+       self._start_service()
+@@ -786,6 +949,10 @@ if not test_javahl:
+     log_file = os.path.join(abs_builddir, log)
+     fail_log_file = os.path.join(abs_builddir, faillog)
+ 
++  if run_httpd:
++    httpd_version = "%.1f" % daemon.httpd_ver
++  else:
++    httpd_version = None
+   th = run_tests.TestHarness(abs_srcdir, abs_builddir,
+                              log_file,
+                              fail_log_file,
+@@ -795,6 +962,7 @@ if not test_javahl:
+                              fsfs_sharding, fsfs_packing,
+                              list_tests, svn_bin, mode_filter,
+                              milestone_filter,
++                             httpd_version=httpd_version,
+                              set_log_level=log_level, ssl_cert=ssl_cert)
+   old_cwd = os.getcwd()
+   try:
only in patch2:
unchanged:
--- subversion-1.8.13.orig/debian/patches/CVE-2015-3187
+++ subversion-1.8.13/debian/patches/CVE-2015-3187
@@ -0,0 +1,404 @@
+  svn_repos_trace_node_locations() reveals paths hidden by authz
+
+Summary
+=======
+
+  Subversion servers, both httpd and svnserve, will reveal some paths
+  that should be hidden by path-based authz.  When a node is copied
+  from an unreadable location to a readable location the unreadable
+  path may be revealed.  This vulnerablity only reveals the path, it
+  does not reveal the contents of the path.
+
+Known vulnerable
+================
+
+  Subversion 1.8.0 to 1.8.13
+  Subversion 1.7.0 to 1.7.20
+  All older versions
+
+Known fixed
+===========
+
+  Subversion 1.8.14
+  Subversion 1.7.21
+
+Details
+=======
+
+  The function svn_repos_trace_node_locations() follows the history of
+  a node through earlier revisions and shows any copies.  It should
+  stop following history when an unreadable path is encountered but an
+  implementation error causes the first unreadable path to be returned
+  in some circumstances.  This only reveals the unreadable path, it
+  does not reveal the content of the file or directory at that path.
+  Any attempts to obtain the content will fail with an authorization
+  error.
+
+  Examples:
+
+  1. After copying "unreadable-path" to "readable-path" then following
+     the history of "readable-path" may reveal "unreadable-path".
+
+  2. After copying "unreadable/some-path" to "readable/other-path"
+     then following the history of "readable/other-path" may reveal
+     "unreadable/some-path".
+
+Severity
+========
+
+  CVSSv2 Base Score: 3.5
+  CVSSv2 Base Vector: AV:N/AC:M/Au:S/C:P/I:N/A:N
+
+Recommendations
+===============
+
+  All Subversion servers using path-based authz to deny read access
+  to sensitive paths should be upgraded.
+
+References
+==========
+
+  CVE-2015-3187 (Subversion)
+
+Reported by
+===========
+
+  C. Michael Pilato, CollabNet
+
+Index: subversion/libsvn_repos/rev_hunt.c
+===================================================================
+--- a/subversion/libsvn_repos/rev_hunt.c	(revision 1685077)
++++ b/subversion/libsvn_repos/rev_hunt.c	(working copy)
+@@ -726,23 +726,6 @@ svn_repos_trace_node_locations(svn_fs_t *fs,
+       if (! prev_path)
+         break;
+ 
+-      if (authz_read_func)
+-        {
+-          svn_boolean_t readable;
+-          svn_fs_root_t *tmp_root;
+-
+-          SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool));
+-          SVN_ERR(authz_read_func(&readable, tmp_root, path,
+-                                  authz_read_baton, currpool));
+-          if (! readable)
+-            {
+-              svn_pool_destroy(lastpool);
+-              svn_pool_destroy(currpool);
+-
+-              return SVN_NO_ERROR;
+-            }
+-        }
+-
+       /* Assign the current path to all younger revisions until we reach
+          the copy target rev. */
+       while ((revision_ptr < revision_ptr_end)
+@@ -765,6 +748,20 @@ svn_repos_trace_node_locations(svn_fs_t *fs,
+       path = prev_path;
+       revision = prev_rev;
+ 
++      if (authz_read_func)
++        {
++          svn_boolean_t readable;
++          SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
++          SVN_ERR(authz_read_func(&readable, root, path,
++                                  authz_read_baton, currpool));
++          if (!readable)
++            {
++              svn_pool_destroy(lastpool);
++              svn_pool_destroy(currpool);
++              return SVN_NO_ERROR;
++            }
++        }
++
+       /* Clear last pool and switch. */
+       svn_pool_clear(lastpool);
+       tmppool = lastpool;
+Index: subversion/tests/cmdline/authz_tests.py
+===================================================================
+--- a/subversion/tests/cmdline/authz_tests.py	(revision 1685077)
++++ b/subversion/tests/cmdline/authz_tests.py	(working copy)
+@@ -609,8 +609,10 @@ def authz_log_and_tracing_test(sbox):
+ 
+   ## cat
+ 
++  expected_err2 = ".*svn: E195012: Unable to find repository location.*"
++
+   # now see if we can look at the older version of rho
+-  svntest.actions.run_and_verify_svn(None, None, expected_err,
++  svntest.actions.run_and_verify_svn(None, None, expected_err2,
+                                      'cat', '-r', '2', D_url+'/rho')
+ 
+   if sbox.repo_url.startswith('http'):
+@@ -627,10 +629,11 @@ def authz_log_and_tracing_test(sbox):
+   svntest.actions.run_and_verify_svn(None, None, expected_err,
+                                      'diff', '-r', 'HEAD', G_url+'/rho')
+ 
+-  svntest.actions.run_and_verify_svn(None, None, expected_err,
++  # diff treats the unreadable path as indicating an add so no error
++  svntest.actions.run_and_verify_svn(None, None, [],
+                                      'diff', '-r', '2', D_url+'/rho')
+ 
+-  svntest.actions.run_and_verify_svn(None, None, expected_err,
++  svntest.actions.run_and_verify_svn(None, None, [],
+                                      'diff', '-r', '2:4', D_url+'/rho')
+ 
+ # test whether read access is correctly granted and denied
+Index: subversion/tests/libsvn_repos/repos-test.c
+===================================================================
+--- a/subversion/tests/libsvn_repos/repos-test.c	(revision 1685077)
++++ b/subversion/tests/libsvn_repos/repos-test.c	(working copy)
+@@ -3524,6 +3524,245 @@ test_load_r0_mergeinfo(const svn_test_opts_t *opts
+   return SVN_NO_ERROR;
+ }
+ 
++static svn_error_t *
++mkdir_delete_copy(svn_repos_t *repos,
++                  const char *src,
++                  const char *dst,
++                  apr_pool_t *pool)
++{
++  svn_fs_t *fs = svn_repos_fs(repos);
++  svn_revnum_t youngest_rev;
++  svn_fs_txn_t *txn;
++  svn_fs_root_t *txn_root, *rev_root;
++
++  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
++  
++  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
++  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
++  SVN_ERR(svn_fs_make_dir(txn_root, "A/T", pool));
++  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
++
++  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
++  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
++  SVN_ERR(svn_fs_delete(txn_root, "A/T", pool));
++  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
++
++  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
++  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
++  SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev - 1, pool));
++  SVN_ERR(svn_fs_copy(rev_root, src, txn_root, dst, pool));
++  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
++
++  return SVN_NO_ERROR;
++}
++
++struct authz_read_baton_t {
++  apr_hash_t *paths;
++  apr_pool_t *pool;
++  const char *deny;
++};
++
++static svn_error_t *
++authz_read_func(svn_boolean_t *allowed,
++                svn_fs_root_t *root,
++                const char *path,
++                void *baton,
++                apr_pool_t *pool)
++{
++  struct authz_read_baton_t *b = baton;
++
++  if (b->deny && !strcmp(b->deny, path))
++    *allowed = FALSE;
++  else
++    *allowed = TRUE;
++
++  svn_hash_sets(b->paths, apr_pstrdup(b->pool, path), (void*)1);
++
++  return SVN_NO_ERROR;
++}
++
++static svn_error_t *
++verify_locations(apr_hash_t *actual,
++                 apr_hash_t *expected,
++                 apr_hash_t *checked,
++                 apr_pool_t *pool)
++{
++  apr_hash_index_t *hi;
++
++  for (hi = apr_hash_first(pool, expected); hi; hi = apr_hash_next(hi))
++    {
++      const svn_revnum_t *rev = svn__apr_hash_index_key(hi);
++      const char *path = apr_hash_get(actual, rev, sizeof(svn_revnum_t));
++
++      if (!path)
++        return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
++                                 "expected %s for %d found (null)",
++                                 (char*)svn__apr_hash_index_val(hi),
++                                 (int)*rev);
++      else if (strcmp(path, svn__apr_hash_index_val(hi)))
++        return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
++                                 "expected %s for %d found %s",
++                                 (char*)svn__apr_hash_index_val(hi),
++                                 (int)*rev, path);
++
++    }
++
++  for (hi = apr_hash_first(pool, actual); hi; hi = apr_hash_next(hi))
++    {
++      const svn_revnum_t *rev = svn__apr_hash_index_key(hi);
++      const char *path = apr_hash_get(expected, rev, sizeof(svn_revnum_t));
++
++      if (!path)
++        return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
++                                 "found %s for %d expected (null)",
++                                 (char*)svn__apr_hash_index_val(hi),
++                                 (int)*rev);
++      else if (strcmp(path, svn__apr_hash_index_val(hi)))
++        return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
++                                 "found %s for %d expected %s",
++                                 (char*)svn__apr_hash_index_val(hi),
++                                 (int)*rev, path);
++
++      if (!svn_hash_gets(checked, path))
++        return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
++                                 "did not check %s", path);
++    }
++
++  return SVN_NO_ERROR;
++}
++
++static void
++set_expected(apr_hash_t *expected,
++             svn_revnum_t rev,
++             const char *path,
++             apr_pool_t *pool)
++{
++  svn_revnum_t *rp = apr_palloc(pool, sizeof(svn_revnum_t));
++  *rp = rev;
++  apr_hash_set(expected, rp, sizeof(svn_revnum_t), path);
++}
++
++static svn_error_t *
++trace_node_locations_authz(const svn_test_opts_t *opts,
++                           apr_pool_t *pool)
++{
++  svn_repos_t *repos;
++  svn_fs_t *fs;
++  svn_revnum_t youngest_rev = 0;
++  svn_fs_txn_t *txn;
++  svn_fs_root_t *txn_root;
++  struct authz_read_baton_t arb;
++  apr_array_header_t *revs = apr_array_make(pool, 10, sizeof(svn_revnum_t));
++  apr_hash_t *locations;
++  apr_hash_t *expected = apr_hash_make(pool);
++  int i;
++
++  /* Create test repository. */
++  SVN_ERR(svn_test__create_repos(&repos, "test-repo-trace-node-locations-authz",
++                                 opts, pool));
++  fs = svn_repos_fs(repos);
++
++  /* r1 create A */
++  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
++  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
++  SVN_ERR(svn_fs_make_dir(txn_root, "A", pool));
++  SVN_ERR(svn_fs_make_file(txn_root, "A/f", pool));
++  SVN_ERR(svn_test__set_file_contents(txn_root, "A/f", "foobar", pool));
++  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
++
++  /* r4 copy A to B */
++  SVN_ERR(mkdir_delete_copy(repos, "A", "B", pool));
++
++  /* r7 copy B to C */
++  SVN_ERR(mkdir_delete_copy(repos, "B", "C", pool));
++
++  /* r10 copy C to D */
++  SVN_ERR(mkdir_delete_copy(repos, "C", "D", pool));
++
++  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
++  SVN_ERR_ASSERT(youngest_rev == 10);
++
++  arb.paths = apr_hash_make(pool);
++  arb.pool = pool;
++  arb.deny = NULL;
++
++  apr_array_clear(revs);
++  for (i = 0; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  set_expected(expected, 10, "/D/f", pool);
++  set_expected(expected, 8, "/C/f", pool);
++  set_expected(expected, 7, "/C/f", pool);
++  set_expected(expected, 5, "/B/f", pool);
++  set_expected(expected, 4, "/B/f", pool);
++  set_expected(expected, 2, "/A/f", pool);
++  set_expected(expected, 1, "/A/f", pool);
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  apr_array_clear(revs);
++  for (i = 1; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  apr_array_clear(revs);
++  for (i = 2; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  set_expected(expected, 1, NULL, pool);
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  apr_array_clear(revs);
++  for (i = 3; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  set_expected(expected, 2, NULL, pool);
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  apr_array_clear(revs);
++  for (i = 6; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  set_expected(expected, 5, NULL, pool);
++  set_expected(expected, 4, NULL, pool);
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  arb.deny = "/B/f";
++  apr_array_clear(revs);
++  for (i = 0; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  apr_array_clear(revs);
++  for (i = 6; i <= youngest_rev; ++i)
++    APR_ARRAY_PUSH(revs, svn_revnum_t) = i;
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  APR_ARRAY_PUSH(revs, svn_revnum_t) = 0;
++  apr_hash_clear(arb.paths);
++  SVN_ERR(svn_repos_trace_node_locations(fs, &locations, "D/f", 10, revs,
++                                         authz_read_func, &arb, pool));
++  SVN_ERR(verify_locations(locations, expected, arb.paths, pool));
++
++  return SVN_NO_ERROR;
++}
++
+ /* The test table.  */
+ 
+ struct svn_test_descriptor_t test_funcs[] =
+@@ -3573,5 +3812,7 @@ struct svn_test_descriptor_t test_funcs[] =
+                        "test dumping with r0 mergeinfo"),
+     SVN_TEST_OPTS_PASS(test_load_r0_mergeinfo,
+                        "test loading with r0 mergeinfo"),
++    SVN_TEST_OPTS_PASS(trace_node_locations_authz,
++                       "authz for svn_repos_trace_node_locations"),
+     SVN_TEST_NULL
+   };

Reply to: