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 <james...@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 <james...@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 + };