You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2015/08/05 22:24:58 UTC

svn commit: r1694321 [2/3] - in /subversion/site/publish/security: CVE-2015-3184-advisory.txt CVE-2015-3187-advisory.txt index.html

Added: subversion/site/publish/security/CVE-2015-3184-advisory.txt
URL: http://svn.apache.org/viewvc/subversion/site/publish/security/CVE-2015-3184-advisory.txt?rev=1694321&view=auto
==============================================================================
--- subversion/site/publish/security/CVE-2015-3184-advisory.txt (added)
+++ subversion/site/publish/security/CVE-2015-3184-advisory.txt Wed Aug  5 20:24:58 2015
@@ -0,0 +1,4273 @@
+  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
+
+Patches
+=======
+
+Patches:
+========
+
+  Patch for Subversion 1.8.13:
+[[[
+Index: Makefile.in
+===================================================================
+--- Makefile.in	(revision 1691883)
++++ Makefile.in	(working copy)
+@@ -357,6 +357,7 @@ TEST_SHLIB_VAR_SWIG_RB=\
+   fi;
+ 
+ APXS = @APXS@
++HTTPD_VERSION = @HTTPD_VERSION@
+ 
+ PYTHON = @PYTHON@
+ PERL = @PERL@
+@@ -509,6 +510,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
+===================================================================
+--- build/ac-macros/apache.m4	(revision 1691883)
++++ 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
+===================================================================
+--- build/run_tests.py	(revision 1691883)
++++ 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
+===================================================================
+--- subversion/mod_authz_svn/mod_authz_svn.c	(revision 1691883)
++++ 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
++
+ /*
+  * Configuration
+  */
+@@ -819,9 +860,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;
+@@ -834,9 +917,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)
+@@ -848,6 +932,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);
+@@ -856,7 +941,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;
+@@ -937,6 +1041,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
++
+ /*
+  * Module flesh
+  */
+@@ -953,6 +1068,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
+===================================================================
+--- subversion/tests/cmdline/README	(revision 1691883)
++++ 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
+===================================================================
+--- subversion/tests/cmdline/davautocheck.sh	(revision 1691883)
++++ 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
+===================================================================
+--- subversion/tests/cmdline/mod_authz_svn_tests.py	(nonexistent)
++++ 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 },

[... 2620 lines stripped ...]