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 block for the DocumentRoot allowing access to everyone (e.g. Require all granted) and "Satisfy any" must not be set. This sort of configuration is included in the default httpd.conf that `make install` provides and is fairly standard. Severity ======== CVSSv2 Base Score: 4.3 CVSSv2 Base Vector: AV:N/AC:M/Au:N/C:P/I:N/A:N The repository needs to be configured with mixed anonymous and authenticated path-based authz and the the attacker needs to know the paths to files in the repository that require authentication. Recommendations =============== All users of mixed anonymous/authenticated authz to upgrade Apache and Subversion. As a workaround disable mixed anonymous/authenticated authz. References ========== CVE-2015-3184 (Subversion) CVE-2015-3185 (httpd) Reported by =========== C. Michael Pilato, CollabNet Patches ======= Patches: ======== Patch for Subversion 1.8.13: [[[ Index: Makefile.in =================================================================== --- Makefile.in (revision 1691883) +++ Makefile.in (working copy) @@ -357,6 +357,7 @@ TEST_SHLIB_VAR_SWIG_RB=\ fi; APXS = @APXS@ +HTTPD_VERSION = @HTTPD_VERSION@ PYTHON = @PYTHON@ PERL = @PERL@ @@ -509,6 +510,9 @@ check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $(TEST_DEPS if test "$(HTTP_LIBRARY)" != ""; then \ flags="--http-library $(HTTP_LIBRARY) $$flags"; \ fi; \ + if test "$(HTTPD_VERSION)" != ""; then \ + flags="--httpd-version $(HTTPD_VERSION) $$flags"; \ + fi; \ if test "$(SERVER_MINOR_VERSION)" != ""; then \ flags="--server-minor-version $(SERVER_MINOR_VERSION) $$flags"; \ fi; \ Index: build/ac-macros/apache.m4 =================================================================== --- build/ac-macros/apache.m4 (revision 1691883) +++ build/ac-macros/apache.m4 (working copy) @@ -160,6 +160,20 @@ if test -n "$APXS" && test "$APXS" != "no"; then BUILD_APACHE_RULE=apache-mod INSTALL_APACHE_RULE=install-mods-shared INSTALL_APACHE_MODS=true + HTTPD="`$APXS -q sbindir`/`$APXS -q PROGNAME`" + if ! test -e $HTTPD ; then + HTTPD="`$APXS -q bindir`/`$APXS -q PROGNAME`" + fi + HTTPD_VERSION=["`$HTTPD -v | $SED -e 's@^.*/\([0-9.]*\)\(.*$\)@\1@ ; 1q'`"] + AC_ARG_ENABLE(broken-httpd-auth, + AS_HELP_STRING([--enable-broken-httpd-auth], + [Allow building against httpd 2.4 with broken auth]), + [broken_httpd_auth=$enableval],[broken_httpd_auth=no]) + if test "$enable_broken_httpd_auth" = "yes"; then + AC_MSG_NOTICE([Building with broken httpd auth]) + AC_DEFINE(SVN_ALLOW_BROKEN_HTTPD_AUTH, 1, + [Defined to allow building against httpd 2.4 with broken auth]) + fi case $host in *-*-cygwin*) @@ -178,6 +192,7 @@ AC_SUBST(APACHE_LDFLAGS) AC_SUBST(APACHE_INCLUDES) AC_SUBST(APACHE_LIBEXECDIR) AC_SUBST(INSTALL_APACHE_MODS) +AC_SUBST(HTTPD_VERSION) # there aren't any flags that interest us ... #if test -n "$APXS" && test "$APXS" != "no"; then Index: build/run_tests.py =================================================================== --- build/run_tests.py (revision 1691883) +++ build/run_tests.py (working copy) @@ -29,6 +29,7 @@ [--fs-type=] [--fsfs-packing] [--fsfs-sharding=] [--list] [--milestone-filter=] [--mode-filter=] [--server-minor-version=] [--http-proxy=:] + [--httpd-version=] [--config-file=] [--ssl-cert=] @@ -125,7 +126,7 @@ class TestHarness: fsfs_sharding=None, fsfs_packing=None, list_tests=None, svn_bin=None, mode_filter=None, milestone_filter=None, set_log_level=None, ssl_cert=None, - http_proxy=None): + http_proxy=None, httpd_version=None): '''Construct a TestHarness instance. ABS_SRCDIR and ABS_BUILDDIR are the source and build directories. @@ -178,6 +179,7 @@ class TestHarness: self.log = None self.ssl_cert = ssl_cert self.http_proxy = http_proxy + self.httpd_version = httpd_version if not sys.stdout.isatty() or sys.platform == 'win32': TextColors.disable() @@ -481,6 +483,8 @@ class TestHarness: svntest.main.options.ssl_cert = self.ssl_cert if self.http_proxy is not None: svntest.main.options.http_proxy = self.http_proxy + if self.httpd_version is not None: + svntest.main.options.httpd_version = self.httpd_version svntest.main.options.srcdir = self.srcdir @@ -645,7 +649,7 @@ def main(): 'enable-sasl', 'parallel', 'config-file=', 'log-to-stdout', 'list', 'milestone-filter=', 'mode-filter=', 'set-log-level=', 'ssl-cert=', - 'http-proxy=']) + 'http-proxy=', 'httpd-version=']) except getopt.GetoptError: args = [] @@ -656,9 +660,9 @@ def main(): base_url, fs_type, verbose, cleanup, enable_sasl, http_library, \ server_minor_version, fsfs_sharding, fsfs_packing, parallel, \ config_file, log_to_stdout, list_tests, mode_filter, milestone_filter, \ - set_log_level, ssl_cert, http_proxy = \ + set_log_level, ssl_cert, http_proxy, httpd_version = \ None, None, None, None, None, None, None, None, None, None, None, \ - None, None, None, None, None, None, None + None, None, None, None, None, None, None, None for opt, val in opts: if opt in ['-u', '--url']: base_url = val @@ -696,6 +700,8 @@ def main(): ssl_cert = val elif opt in ['--http-proxy']: http_proxy = val + elif opt in ['--httpd-version']: + httpd_version = val else: raise getopt.GetoptError @@ -712,7 +718,7 @@ def main(): fsfs_sharding, fsfs_packing, list_tests, mode_filter=mode_filter, milestone_filter=milestone_filter, set_log_level=set_log_level, ssl_cert=ssl_cert, - http_proxy=http_proxy) + http_proxy=http_proxy, httpd_version=httpd_version) failed = th.run(args[2:]) if failed: Index: subversion/mod_authz_svn/mod_authz_svn.c =================================================================== --- subversion/mod_authz_svn/mod_authz_svn.c (revision 1691883) +++ subversion/mod_authz_svn/mod_authz_svn.c (working copy) @@ -48,6 +48,23 @@ #include "svn_dirent_uri.h" #include "private/svn_fspath.h" +/* The apache headers define these and they conflict with our definitions. */ +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif +#include "svn_private_config.h" #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(authz_svn); @@ -67,6 +84,30 @@ typedef struct authz_svn_config_rec { const char *force_username_case; } authz_svn_config_rec; +#if AP_MODULE_MAGIC_AT_LEAST(20060110,0) /* version where + ap_some_auth_required breaks */ +# if AP_MODULE_MAGIC_AT_LEAST(20120211,47) /* first version with + force_authn hook and + ap_some_authn_required() which + allows us to work without + ap_some_auth_required() */ +# define USE_FORCE_AUTHN 1 +# define IN_SOME_AUTHN_NOTE "authz_svn-in-some-authn" +# define FORCE_AUTHN_NOTE "authz_svn-force-authn" +# else + /* ap_some_auth_required() is busted and no viable alternative exists */ +# ifndef SVN_ALLOW_BROKEN_HTTPD_AUTH +# error This version of httpd has a security hole with mod_authz_svn +# else + /* user wants to build anyway */ +# define USE_FORCE_AUTHN 0 +# endif +# endif +#else + /* old enough that ap_some_auth_required() still works */ +# define USE_FORCE_AUTHN 0 +#endif + /* * Configuration */ @@ -819,9 +860,51 @@ access_checker(request_rec *r) &authz_svn_module); const char *repos_path = NULL; const char *dest_repos_path = NULL; - int status; + int status, authn_required; +#if USE_FORCE_AUTHN + /* Use the force_authn() hook available in 2.4.x to work securely + * given that ap_some_auth_required() is no longer functional for our + * purposes in 2.4.x. + */ + int authn_configured; + /* We are not configured to run */ + if (!conf->anonymous || apr_table_get(r->notes, IN_SOME_AUTHN_NOTE) + || (! (conf->access_file || conf->repo_relative_access_file))) + return DECLINED; + + /* Authentication is configured */ + authn_configured = ap_auth_type(r) != NULL; + if (authn_configured) + { + /* If the user is trying to authenticate, let him. It doesn't + * make much sense to grant anonymous access but deny authenticated + * users access, even though you can do that with '$anon' in the + * access file. + */ + if (apr_table_get(r->headers_in, + (PROXYREQ_PROXY == r->proxyreq) + ? "Proxy-Authorization" : "Authorization")) + { + /* Set the note to force authn regardless of what access_checker_ex + hook requires */ + apr_table_setn(r->notes, FORCE_AUTHN_NOTE, (const char*)1); + + /* provide the proper return so the access_checker hook doesn't + * prevent the code from continuing on to the other auth hooks */ + if (ap_satisfies(r) != SATISFY_ANY) + return OK; + else + return HTTP_FORBIDDEN; + } + } + +#else + /* Support for older versions of httpd that have a working + * ap_some_auth_required() */ + + /* We are not configured to run */ if (!conf->anonymous || (! (conf->access_file || conf->repo_relative_access_file))) return DECLINED; @@ -834,9 +917,10 @@ access_checker(request_rec *r) if (ap_satisfies(r) != SATISFY_ANY) return DECLINED; - /* If the user is trying to authenticate, let him. If anonymous - * access is allowed, so is authenticated access, by definition - * of the meaning of '*' in the access file. + /* If the user is trying to authenticate, let him. It doesn't + * make much sense to grant anonymous access but deny authenticated + * users access, even though you can do that with '$anon' in the + * access file. */ if (apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq) @@ -848,6 +932,7 @@ access_checker(request_rec *r) return HTTP_FORBIDDEN; } } +#endif /* If anon access is allowed, return OK */ status = req_check_access(r, conf, &repos_path, &dest_repos_path); @@ -856,7 +941,26 @@ access_checker(request_rec *r) if (!conf->authoritative) return DECLINED; +#if USE_FORCE_AUTHN + if (authn_configured) { + /* We have to check to see if authn is required because if so we must + * return UNAUTHORIZED (401) rather than FORBIDDEN (403) since returning + * the 403 leaks information about what paths may exist to + * unauthenticated users. We must set a note here in order + * to use ap_some_authn_rquired() without triggering an infinite + * loop since the call will trigger this function to be called again. */ + apr_table_setn(r->notes, IN_SOME_AUTHN_NOTE, (const char*)1); + authn_required = ap_some_authn_required(r); + apr_table_unset(r->notes, IN_SOME_AUTHN_NOTE); + if (authn_required) + { + ap_note_auth_failure(r); + return HTTP_UNAUTHORIZED; + } + } +#else if (!ap_some_auth_required(r)) +#endif log_access_verdict(APLOG_MARK, r, 0, repos_path, dest_repos_path); return HTTP_FORBIDDEN; @@ -937,6 +1041,17 @@ auth_checker(request_rec *r) return OK; } +#if USE_FORCE_AUTHN +static int +force_authn(request_rec *r) +{ + if (apr_table_get(r->notes, FORCE_AUTHN_NOTE)) + return OK; + + return DECLINED; +} +#endif + /* * Module flesh */ @@ -953,6 +1068,9 @@ register_hooks(apr_pool_t *p) * give SSLOptions +FakeBasicAuth a chance to work. */ ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST); ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST); +#if USE_FORCE_AUTHN + ap_hook_force_authn(force_authn, NULL, NULL, APR_HOOK_FIRST); +#endif ap_register_provider(p, AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP, AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME, Index: subversion/tests/cmdline/README =================================================================== --- subversion/tests/cmdline/README (revision 1691883) +++ subversion/tests/cmdline/README (working copy) @@ -83,6 +83,133 @@ paths adjusted appropriately: Require valid-user + + 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. + + Require all granted + + + Allow from all + + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + + 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 + + Require valid-user + Require expr req('ALLOW') == '1' + + + + 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 + + Require valid-user + Require expr req('ALLOW') == '1' + + + + + RedirectMatch permanent ^/svn-test-work/repositories/REDIRECT-PERM-(.*)$ /svn-test-work/repositories/$1 RedirectMatch ^/svn-test-work/repositories/REDIRECT-TEMP-(.*)$ /svn-test-work/repositories/$1 @@ -101,8 +228,17 @@ just drop the following 2-line snippet into the ---------------------------- jrandom:xCGl35kV9oWCY jconstant:xCGl35kV9oWCY +JRANDOM:xCGl35kV9oWCY +JCONSTANT:xCGl35kV9oWCY ---------------------------- +and these lines into the +/usr/local/apache/conf/groups file: +---------------------------- +random: jrandom +constant: jconstant +---------------------------- + Now, (re)start Apache and run the tests over mod_dav_svn. You can run a test script over DAV: @@ -138,6 +274,8 @@ Note [1]: It would be quite too much to expect tho ---------------------------- jrandom:$apr1$3p1.....$FQW6RceW5QhJ2blWDQgKn0 jconstant:$apr1$jp1.....$Usrqji1c9H6AbOxOGAzzb0 + JRANDOM:$apr1$3p1.....$FQW6RceW5QhJ2blWDQgKn0 + JCONSTANT:$apr1$jp1.....$Usrqji1c9H6AbOxOGAzzb0 ---------------------------- Index: subversion/tests/cmdline/davautocheck.sh =================================================================== --- subversion/tests/cmdline/davautocheck.sh (revision 1691883) +++ subversion/tests/cmdline/davautocheck.sh (working copy) @@ -289,8 +289,6 @@ LOAD_MOD_AUTHN_CORE="$(get_loadmodule_config mod_a || fail "Authn_Core module not found." LOAD_MOD_AUTHZ_CORE="$(get_loadmodule_config mod_authz_core)" \ || fail "Authz_Core module not found." -LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \ - || fail "Authz_Host module not found." LOAD_MOD_UNIXD=$(get_loadmodule_config mod_unixd) \ || fail "UnixD module not found" } @@ -298,6 +296,10 @@ LOAD_MOD_AUTHN_FILE="$(get_loadmodule_config mod_a || fail "Authn_File module not found." LOAD_MOD_AUTHZ_USER="$(get_loadmodule_config mod_authz_user)" \ || fail "Authz_User module not found." +LOAD_MOD_AUTHZ_GROUPFILE="$(get_loadmodule_config mod_authz_groupfile)" \ + || fail "Authz_GroupFile module not found." +LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \ + || fail "Authz_Host module not found." } if [ ${APACHE_MPM:+set} ]; then LOAD_MOD_MPM=$(get_loadmodule_config mod_mpm_$APACHE_MPM) \ @@ -328,6 +330,7 @@ HTTPD_ERROR_LOG="$HTTPD_ROOT/error_log" HTTPD_MIME_TYPES="$HTTPD_ROOT/mime.types" BASE_URL="http://localhost:$HTTPD_PORT" HTTPD_USERS="$HTTPD_ROOT/users" +HTTPD_GROUPS="$HTTPD_ROOT/groups" mkdir "$HTTPD_ROOT" \ || fail "couldn't create temporary directory '$HTTPD_ROOT'" @@ -388,6 +391,14 @@ fi say "Adding users for lock authentication" $HTPASSWD -bc $HTTPD_USERS jrandom rayjandom $HTPASSWD -b $HTTPD_USERS jconstant rayjandom +$HTPASSWD -b $HTTPD_USERS JRANDOM rayjandom +$HTPASSWD -b $HTTPD_USERS JCONSTANT rayjandom + +say "Adding groups for mod_authz_svn tests" +cat > "$HTTPD_GROUPS" <<__EOF__ +random: jrandom +constant: jconstant +__EOF__ touch $HTTPD_MIME_TYPES @@ -405,7 +416,9 @@ $LOAD_MOD_AUTHN_CORE $LOAD_MOD_AUTHN_FILE $LOAD_MOD_AUTHZ_CORE $LOAD_MOD_AUTHZ_USER +$LOAD_MOD_AUTHZ_GROUPFILE $LOAD_MOD_AUTHZ_HOST +$LOAD_MOD_ACCESS_COMPAT LoadModule authz_svn_module "$MOD_AUTHZ_SVN" __EOF__ @@ -497,6 +510,161 @@ CustomLog "$HTTPD_ROOT/ops" "%t %u %{SVN SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL} ${SVN_PATH_AUTHZ_LINE} + + 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. + + Require all granted + + + Allow from all + + ${SVN_PATH_AUTHZ_LINE} + + + 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} + + + 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 + + + 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} + + + 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 + + + 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} + + + 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} + + + 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 + + + + 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 + + Require valid-user + Require expr req('ALLOW') == '1' + + ${SVN_PATH_AUTHZ_LINE} + + + 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 + + Require valid-user + Require expr req('ALLOW') == '1' + + ${SVN_PATH_AUTHZ_LINE} + + RedirectMatch permanent ^/svn-test-work/repositories/REDIRECT-PERM-(.*)\$ /svn-test-work/repositories/\$1 RedirectMatch ^/svn-test-work/repositories/REDIRECT-TEMP-(.*)\$ /svn-test-work/repositories/\$1 __EOF__ Index: subversion/tests/cmdline/mod_authz_svn_tests.py =================================================================== --- subversion/tests/cmdline/mod_authz_svn_tests.py (nonexistent) +++ subversion/tests/cmdline/mod_authz_svn_tests.py (working copy) @@ -0,0 +1,1073 @@ +#!/usr/bin/env python +# +# mod_authz_svn_tests.py: testing mod_authz_svn +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import os, re, logging + +logger = logging.getLogger() + +# Our testing module +import svntest + +# (abbreviation) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco + +ls_of_D_no_H = '''repos - Revision 1: /A/D + +

repos - Revision 1: /A/D

+ +''' + +ls_of_D_H = '''repos - Revision 1: /A/D + +

repos - Revision 1: /A/D

+ +''' + +ls_of_H = '''repos - Revision 1: /A/D/H + +

repos - Revision 1: /A/D/H

+ +''' + +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 =================================================================== --- subversion/tests/cmdline/svntest/main.py (revision 1691883) +++ 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 =================================================================== --- win-tests.py (revision 1691883) +++ 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' \ '\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 \ + '' + '\n' \ + ' DAV svn' + '\n' \ + ' SVNParentPath ' + local_tmp + '\n' \ + ' AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \ + ' SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \ + ' SVNListParentPath On' + '\n' \ + ' ' + '\n' \ + ' Require all granted' + '\n' \ + ' ' + '\n' \ + ' ' + '\n' \ + ' Allow from all' + '\n' \ + ' ' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\n' \ + '' + '\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' \ + ' ' + '\n' \ + ' Require valid-user' + '\n' \ + ' Require expr req(\'ALLOW\') == \'1\'' + '\n' \ + ' ' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ + ''+ '\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' \ + ' ' + '\n' \ + ' Require valid-user' + '\n' \ + ' Require expr req(\'ALLOW\') == \'1\'' + '\n' \ + ' ' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ + '' + '\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: ]]] Patch for Subversion 1.7.20: [[[ Index: Makefile.in =================================================================== --- Makefile.in (revision 1691883) +++ Makefile.in (working copy) @@ -316,6 +316,7 @@ INSTALL_EXTRA_SWIG_RB=\ done APXS = @APXS@ +HTTPD_VERSION = @HTTPD_VERSION@ PYTHON = @PYTHON@ PERL = @PERL@ @@ -463,6 +464,9 @@ check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $(TEST_DEPS if test "$(HTTP_LIBRARY)" != ""; then \ flags="--http-library $(HTTP_LIBRARY) $$flags"; \ fi; \ + if test "$(HTTPD_VERSION)" != ""; then \ + flags="--httpd-version $(HTTPD_VERSION) $$flags"; \ + fi; \ if test "$(SERVER_MINOR_VERSION)" != ""; then \ flags="--server-minor-version $(SERVER_MINOR_VERSION) $$flags"; \ fi; \ Index: build/ac-macros/apache.m4 =================================================================== --- build/ac-macros/apache.m4 (revision 1691883) +++ build/ac-macros/apache.m4 (working copy) @@ -85,6 +85,20 @@ VERSION_OKAY AC_MSG_RESULT(no - Unable to locate $APXS_INCLUDE/mod_dav.h) APXS="" fi + 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 else AC_MSG_RESULT(no) fi @@ -178,6 +192,7 @@ AC_SUBST(APXS) AC_SUBST(APACHE_LDFLAGS) AC_SUBST(APACHE_INCLUDES) AC_SUBST(APACHE_LIBEXECDIR) +AC_SUBST(HTTPD_VERSION) # there aren't any flags that interest us ... #if test -n "$APXS" && test "$APXS" != "no"; then Index: build/run_tests.py =================================================================== --- build/run_tests.py (revision 1691883) +++ build/run_tests.py (working copy) @@ -29,6 +29,7 @@ [--fs-type=] [--fsfs-packing] [--fsfs-sharding=] [--list] [--milestone-filter=] [--mode-filter=] [--server-minor-version=] + [--httpd-version=] [--config-file=] @@ -81,7 +82,7 @@ class TestHarness: cleanup=None, enable_sasl=None, parallel=None, config_file=None, fsfs_sharding=None, fsfs_packing=None, list_tests=None, svn_bin=None, mode_filter=None, - milestone_filter=None): + milestone_filter=None, httpd_version=None): '''Construct a TestHarness instance. ABS_SRCDIR and ABS_BUILDDIR are the source and build directories. @@ -130,6 +131,7 @@ class TestHarness: self.svn_bin = svn_bin self.mode_filter = mode_filter self.log = None + self.httpd_version = httpd_version if not sys.stdout.isatty() or sys.platform == 'win32': TextColors.disable() @@ -414,6 +416,8 @@ class TestHarness: svntest.main.options.fsfs_packing = self.fsfs_packing if self.mode_filter is not None: svntest.main.options.mode_filter = self.mode_filter + if self.httpd_version is not None: + svntest.main.options.httpd_version = self.httpd_version svntest.main.options.srcdir = self.srcdir @@ -562,7 +566,7 @@ def main(): 'fsfs-packing', 'fsfs-sharding=', 'enable-sasl', 'parallel', 'config-file=', 'log-to-stdout', 'list', 'milestone-filter=', - 'mode-filter=']) + 'mode-filter=', 'httpd-version=']) except getopt.GetoptError: args = [] @@ -572,9 +576,10 @@ 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= \ + config_file, log_to_stdout, list_tests, mode_filter, milestone_filter, \ + httpd_version = \ 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 @@ -606,6 +611,8 @@ def main(): milestone_filter = val elif opt in ['--mode-filter']: mode_filter = val + elif opt in ['--httpd-version']: + httpd_version = val else: raise getopt.GetoptError @@ -620,7 +627,8 @@ def main(): base_url, fs_type, http_library, server_minor_version, verbose, cleanup, enable_sasl, parallel, config_file, fsfs_sharding, fsfs_packing, list_tests, - mode_filter=mode_filter, milestone_filter=milestone_filter) + mode_filter=mode_filter, milestone_filter=milestone_filter, + httpd_version=httpd_version) failed = th.run(args[2:]) if failed: Index: subversion/mod_authz_svn/mod_authz_svn.c =================================================================== --- subversion/mod_authz_svn/mod_authz_svn.c (revision 1691883) +++ subversion/mod_authz_svn/mod_authz_svn.c (working copy) @@ -48,6 +48,23 @@ #include "svn_dirent_uri.h" #include "private/svn_fspath.h" +/* The apache headers define these and they conflict with our definitions. */ +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif +#include "svn_private_config.h" extern module AP_MODULE_DECLARE_DATA authz_svn_module; @@ -65,6 +82,30 @@ typedef struct authz_svn_config_rec { const char *force_username_case; } authz_svn_config_rec; +#if AP_MODULE_MAGIC_AT_LEAST(20060110,0) /* version where + ap_some_auth_required breaks */ +# if AP_MODULE_MAGIC_AT_LEAST(20120211,47) /* first version with + force_authn hook and + ap_some_authn_required() which + allows us to work without + ap_some_auth_required() */ +# define USE_FORCE_AUTHN 1 +# define IN_SOME_AUTHN_NOTE "authz_svn-in-some-authn" +# define FORCE_AUTHN_NOTE "authz_svn-force-authn" +# else + /* ap_some_auth_required() is busted and no viable alternative exists */ +# ifndef SVN_ALLOW_BROKEN_HTTPD_AUTH +# error This version of httpd has a security hole with mod_authz_svn +# else + /* user wants to build anyway */ +# define USE_FORCE_AUTHN 0 +# endif +# endif +#else + /* old enough that ap_some_auth_required() still works */ +# define USE_FORCE_AUTHN 0 +#endif + /* * Configuration */ @@ -682,9 +723,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; @@ -697,9 +780,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) @@ -711,6 +795,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); @@ -719,7 +804,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; @@ -800,6 +904,17 @@ auth_checker(request_rec *r) return OK; } +#if USE_FORCE_AUTHN +static int +force_authn(request_rec *r) +{ + if (apr_table_get(r->notes, FORCE_AUTHN_NOTE)) + return OK; + + return DECLINED; +} +#endif + /* * Module flesh */ @@ -816,6 +931,9 @@ register_hooks(apr_pool_t *p) * give SSLOptions +FakeBasicAuth a chance to work. */ ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST); ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST); +#if USE_FORCE_AUTHN + ap_hook_force_authn(force_authn, NULL, NULL, APR_HOOK_FIRST); +#endif ap_register_provider(p, AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP, AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME, Index: subversion/tests/cmdline/README =================================================================== --- subversion/tests/cmdline/README (revision 1691883) +++ subversion/tests/cmdline/README (working copy) @@ -83,6 +83,133 @@ paths adjusted appropriately: Require valid-user + + 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. + + Require all granted + + + Allow from all + + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + + 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 + + Require valid-user + Require expr req('ALLOW') == '1' + + + + 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 + + Require valid-user + Require expr req('ALLOW') == '1' + + + + + RedirectMatch permanent ^/svn-test-work/repositories/REDIRECT-PERM-(.*)$ /svn-test-work/repositories/$1 RedirectMatch ^/svn-test-work/repositories/REDIRECT-TEMP-(.*)$ /svn-test-work/repositories/$1 @@ -101,8 +228,17 @@ just drop the following 2-line snippet into the ---------------------------- jrandom:xCGl35kV9oWCY jconstant:xCGl35kV9oWCY +JRANDOM:xCGl35kV9oWCY +JCONSTANT:xCGl35kV9oWCY ---------------------------- +and these lines into the +/usr/local/apache/conf/groups file: +---------------------------- +random: jrandom +constant: jconstant +---------------------------- + Now, (re)start Apache and run the tests over mod_dav_svn. You can run a test script over DAV: @@ -138,6 +274,8 @@ Note [1]: It would be quite too much to expect tho ---------------------------- jrandom:$apr1$3p1.....$FQW6RceW5QhJ2blWDQgKn0 jconstant:$apr1$jp1.....$Usrqji1c9H6AbOxOGAzzb0 + JRANDOM:$apr1$3p1.....$FQW6RceW5QhJ2blWDQgKn0 + JCONSTANT:$apr1$jp1.....$Usrqji1c9H6AbOxOGAzzb0 ---------------------------- Index: subversion/tests/cmdline/davautocheck.sh =================================================================== --- subversion/tests/cmdline/davautocheck.sh (revision 1691883) +++ subversion/tests/cmdline/davautocheck.sh (working copy) @@ -248,8 +248,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" } @@ -257,6 +255,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) \ @@ -272,6 +274,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'" @@ -281,6 +284,14 @@ say "Using directory '$HTTPD_ROOT'..." 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 @@ -297,7 +308,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__ @@ -377,6 +390,151 @@ CustomLog "$HTTPD_ROOT/ops" "%t %u %{SVN SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL} ${SVN_PATH_AUTHZ_LINE} + + 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} + 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. + + Require all granted + + + Allow from all + + ${SVN_PATH_AUTHZ_LINE} + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + Require valid-user + Satisfy Any + ${SVN_PATH_AUTHZ_LINE} + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + Require valid-user + AuthzSVNNoAuthWhenAnonymousAllowed On + SVNPathAuthz On + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + Require valid-user + ${SVN_PATH_AUTHZ_LINE} + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + Require valid-user + AuthzSVNAnonymous Off + SVNPathAuthz On + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + Require valid-user + AuthzForceUsernameCase Lower + ${SVN_PATH_AUTHZ_LINE} + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + Require valid-user + AuthzForceUsernameCase Lower + ${SVN_PATH_AUTHZ_LINE} + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + AuthGroupFile $HTTPD_GROUPS + Require group random + AuthzSVNAuthoritative Off + SVNPathAuthz On + + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + AuthzSendForbiddenOnFailure On + Satisfy All + + Require valid-user + Require expr req('ALLOW') == '1' + + ${SVN_PATH_AUTHZ_LINE} + + + 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} + SVNListParentPath On + AuthType Basic + AuthName "Subversion Repository" + AuthUserFile $HTTPD_USERS + AuthzSendForbiddenOnFailure On + Satisfy All + + Require valid-user + Require expr req('ALLOW') == '1' + + ${SVN_PATH_AUTHZ_LINE} + + RedirectMatch permanent ^/svn-test-work/repositories/REDIRECT-PERM-(.*)\$ /svn-test-work/repositories/\$1 RedirectMatch ^/svn-test-work/repositories/REDIRECT-TEMP-(.*)\$ /svn-test-work/repositories/\$1 __EOF__ Index: subversion/tests/cmdline/mod_authz_svn_tests.py =================================================================== --- subversion/tests/cmdline/mod_authz_svn_tests.py (nonexistent) +++ subversion/tests/cmdline/mod_authz_svn_tests.py (working copy) @@ -0,0 +1,1073 @@ +#!/usr/bin/env python +# +# mod_authz_svn_tests.py: testing mod_authz_svn +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import os, re, logging + +logger = logging.getLogger() + +# Our testing module +import svntest + +# (abbreviation) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco + +ls_of_D_no_H = '''repos - Revision 1: /A/D + +

repos - Revision 1: /A/D

+ +''' + +ls_of_D_H = '''repos - Revision 1: /A/D + +

repos - Revision 1: /A/D

+ +''' + +ls_of_H = '''repos - Revision 1: /A/D/H + +

repos - Revision 1: /A/D/H

+ +''' + +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 =================================================================== --- subversion/tests/cmdline/svntest/main.py (revision 1691883) +++ subversion/tests/cmdline/svntest/main.py (working copy) @@ -1148,6 +1148,30 @@ def server_enforces_date_syntax(): def server_has_atomic_revprop(): return options.server_minor_version >= 7 + +# 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 + ###################################################################### @@ -1194,6 +1218,8 @@ class TestSpawningThread(threading.Thread): args.append('--mode-filter=' + options.mode_filter) if options.milestone_filter: args.append('--milestone-filter=' + options.milestone_filter) + if options.httpd_version: + args.append('--httpd-version=' + options.httpd_version) result, stdout_lines, stderr_lines = spawn_process(command, 0, 0, None, *args) @@ -1361,5 +1387,35 @@ class TestRunner: sandbox.cleanup_test_paths() return exit_code + +# 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 + +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 Index: win-tests.py =================================================================== --- win-tests.py (revision 1691883) +++ win-tests.py (working copy) @@ -466,6 +466,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) @@ -479,6 +480,7 @@ class Httpd: create_target_dir(self.root_dir) self._create_users_file() + self._create_groups_file() self._create_mime_types_file() # Determine version. @@ -520,6 +522,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')) @@ -533,6 +537,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/' @@ -562,7 +567,18 @@ class Httpd: 'jrandom', 'rayjandom']) os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-mb', 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') @@ -595,6 +611,153 @@ class Httpd: ' Require valid-user\n' \ '\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 \ + '' + '\n' \ + ' DAV svn' + '\n' \ + ' SVNParentPath ' + local_tmp + '\n' \ + ' AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \ + ' SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \ + ' SVNListParentPath On' + '\n' \ + ' ' + '\n' \ + ' Require all granted' + '\n' \ + ' ' + '\n' \ + ' ' + '\n' \ + ' Allow from all' + '\n' \ + ' ' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\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' \ + '' + '\n' \ + '' + '\n' \ + '' + '\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' \ + ' ' + '\n' \ + ' Require valid-user' + '\n' \ + ' Require expr req(\'ALLOW\') == \'1\'' + '\n' \ + ' ' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ + ''+ '\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' \ + ' ' + '\n' \ + ' Require valid-user' + '\n' \ + ' Require expr req(\'ALLOW\') == \'1\'' + '\n' \ + ' ' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ + '' + '\n' \ + def start(self): if self.service: self._start_service() @@ -728,6 +891,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, @@ -736,7 +903,8 @@ if not test_javahl: cleanup, enable_sasl, parallel, config_file, fsfs_sharding, fsfs_packing, list_tests, svn_bin, mode_filter, - milestone_filter) + milestone_filter, + httpd_version=httpd_version) old_cwd = os.getcwd() try: os.chdir(abs_builddir) ]]]