Fix CVE-2015-3184 Patch is from: http://subversion.apache.org/security/CVE-2015-3184-advisory.txt Upstream-Status: Backport Signed-off-by: Wenzong Fan Index: Makefile.in =================================================================== --- a/Makefile.in (revision 1691883) +++ b/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 =================================================================== --- a/build/ac-macros/apache.m4 (revision 1691883) +++ b/build/ac-macros/apache.m4 (working copy) @@ -160,6 +160,20 @@ if test -n "$APXS" && test "$APXS" != "no"; then BUILD_APACHE_RULE=apache-mod INSTALL_APACHE_RULE=install-mods-shared INSTALL_APACHE_MODS=true + HTTPD="`$APXS -q sbindir`/`$APXS -q PROGNAME`" + if ! test -e $HTTPD ; then + HTTPD="`$APXS -q bindir`/`$APXS -q PROGNAME`" + fi + HTTPD_VERSION=["`$HTTPD -v | $SED -e 's@^.*/\([0-9.]*\)\(.*$\)@\1@ ; 1q'`"] + AC_ARG_ENABLE(broken-httpd-auth, + AS_HELP_STRING([--enable-broken-httpd-auth], + [Allow building against httpd 2.4 with broken auth]), + [broken_httpd_auth=$enableval],[broken_httpd_auth=no]) + if test "$enable_broken_httpd_auth" = "yes"; then + AC_MSG_NOTICE([Building with broken httpd auth]) + AC_DEFINE(SVN_ALLOW_BROKEN_HTTPD_AUTH, 1, + [Defined to allow building against httpd 2.4 with broken auth]) + fi case $host in *-*-cygwin*) @@ -178,6 +192,7 @@ AC_SUBST(APACHE_LDFLAGS) AC_SUBST(APACHE_INCLUDES) AC_SUBST(APACHE_LIBEXECDIR) AC_SUBST(INSTALL_APACHE_MODS) +AC_SUBST(HTTPD_VERSION) # there aren't any flags that interest us ... #if test -n "$APXS" && test "$APXS" != "no"; then Index: build/run_tests.py =================================================================== --- a/build/run_tests.py (revision 1691883) +++ b/build/run_tests.py (working copy) @@ -29,6 +29,7 @@ [--fs-type=] [--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 =================================================================== --- a/subversion/mod_authz_svn/mod_authz_svn.c (revision 1691883) +++ b/subversion/mod_authz_svn/mod_authz_svn.c (working copy) @@ -48,6 +48,23 @@ #include "svn_dirent_uri.h" #include "private/svn_fspath.h" +/* The apache headers define these and they conflict with our definitions. */ +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif +#include "svn_private_config.h" #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(authz_svn); @@ -67,6 +84,30 @@ typedef struct authz_svn_config_rec { const char *force_username_case; } authz_svn_config_rec; +#if AP_MODULE_MAGIC_AT_LEAST(20060110,0) /* version where + ap_some_auth_required breaks */ +# if AP_MODULE_MAGIC_AT_LEAST(20120211,47) /* first version with + force_authn hook and + ap_some_authn_required() which + allows us to work without + ap_some_auth_required() */ +# define USE_FORCE_AUTHN 1 +# define IN_SOME_AUTHN_NOTE "authz_svn-in-some-authn" +# define FORCE_AUTHN_NOTE "authz_svn-force-authn" +# else + /* ap_some_auth_required() is busted and no viable alternative exists */ +# ifndef SVN_ALLOW_BROKEN_HTTPD_AUTH +# error This version of httpd has a security hole with mod_authz_svn +# else + /* user wants to build anyway */ +# define USE_FORCE_AUTHN 0 +# endif +# endif +#else + /* old enough that ap_some_auth_required() still works */ +# define USE_FORCE_AUTHN 0 +#endif + /* * 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 =================================================================== --- a/subversion/tests/cmdline/README (revision 1691883) +++ b/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 =================================================================== --- a/subversion/tests/cmdline/davautocheck.sh (revision 1691883) +++ b/subversion/tests/cmdline/davautocheck.sh (working copy) @@ -289,8 +289,6 @@ LOAD_MOD_AUTHN_CORE="$(get_loadmodule_config mod_a || fail "Authn_Core module not found." LOAD_MOD_AUTHZ_CORE="$(get_loadmodule_config mod_authz_core)" \ || fail "Authz_Core module not found." -LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \ - || fail "Authz_Host module not found." LOAD_MOD_UNIXD=$(get_loadmodule_config mod_unixd) \ || fail "UnixD module not found" } @@ -298,6 +296,10 @@ LOAD_MOD_AUTHN_FILE="$(get_loadmodule_config mod_a || fail "Authn_File module not found." LOAD_MOD_AUTHZ_USER="$(get_loadmodule_config mod_authz_user)" \ || fail "Authz_User module not found." +LOAD_MOD_AUTHZ_GROUPFILE="$(get_loadmodule_config mod_authz_groupfile)" \ + || fail "Authz_GroupFile module not found." +LOAD_MOD_AUTHZ_HOST="$(get_loadmodule_config mod_authz_host)" \ + || fail "Authz_Host module not found." } if [ ${APACHE_MPM:+set} ]; then LOAD_MOD_MPM=$(get_loadmodule_config mod_mpm_$APACHE_MPM) \ @@ -328,6 +330,7 @@ HTTPD_ERROR_LOG="$HTTPD_ROOT/error_log" HTTPD_MIME_TYPES="$HTTPD_ROOT/mime.types" BASE_URL="http://localhost:$HTTPD_PORT" HTTPD_USERS="$HTTPD_ROOT/users" +HTTPD_GROUPS="$HTTPD_ROOT/groups" mkdir "$HTTPD_ROOT" \ || fail "couldn't create temporary directory '$HTTPD_ROOT'" @@ -388,6 +391,14 @@ fi say "Adding users for lock authentication" $HTPASSWD -bc $HTTPD_USERS jrandom rayjandom $HTPASSWD -b $HTTPD_USERS jconstant rayjandom +$HTPASSWD -b $HTTPD_USERS JRANDOM rayjandom +$HTPASSWD -b $HTTPD_USERS JCONSTANT rayjandom + +say "Adding groups for mod_authz_svn tests" +cat > "$HTTPD_GROUPS" <<__EOF__ +random: jrandom +constant: jconstant +__EOF__ touch $HTTPD_MIME_TYPES @@ -405,7 +416,9 @@ $LOAD_MOD_AUTHN_CORE $LOAD_MOD_AUTHN_FILE $LOAD_MOD_AUTHZ_CORE $LOAD_MOD_AUTHZ_USER +$LOAD_MOD_AUTHZ_GROUPFILE $LOAD_MOD_AUTHZ_HOST +$LOAD_MOD_ACCESS_COMPAT LoadModule authz_svn_module "$MOD_AUTHZ_SVN" __EOF__ @@ -497,6 +510,161 @@ CustomLog "$HTTPD_ROOT/ops" "%t %u %{SVN SVNAdvertiseV2Protocol ${ADVERTISE_V2_PROTOCOL} ${SVN_PATH_AUTHZ_LINE} + + 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 =================================================================== --- a/subversion/tests/cmdline/mod_authz_svn_tests.py (nonexistent) +++ b/subversion/tests/cmdline/mod_authz_svn_tests.py (working copy) @@ -0,0 +1,1073 @@ +#!/usr/bin/env python +# +# mod_authz_svn_tests.py: testing mod_authz_svn +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import os, re, logging + +logger = logging.getLogger() + +# Our testing module +import svntest + +# (abbreviation) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco + +ls_of_D_no_H = '''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 =================================================================== --- a/subversion/tests/cmdline/svntest/main.py (revision 1691883) +++ b/subversion/tests/cmdline/svntest/main.py (working copy) @@ -1378,6 +1378,30 @@ def is_plaintext_password_storage_disabled(): return False return True + +# https://issues.apache.org/bugzilla/show_bug.cgi?id=56480 +# https://issues.apache.org/bugzilla/show_bug.cgi?id=55397 +__mod_dav_url_quoting_broken_versions = frozenset([ + '2.2.27', + '2.2.26', + '2.2.25', + '2.4.9', + '2.4.8', + '2.4.7', + '2.4.6', + '2.4.5', +]) +def is_mod_dav_url_quoting_broken(): + if is_ra_type_dav(): + return (options.httpd_version in __mod_dav_url_quoting_broken_versions) + return None + +def is_httpd_authz_provider_enabled(): + if is_ra_type_dav(): + v = options.httpd_version.split('.') + return (v[0] == '2' and int(v[1]) >= 3) or int(v[0]) > 2 + return None + ###################################################################### @@ -1435,6 +1459,8 @@ class TestSpawningThread(threading.Thread): args.append('--ssl-cert=' + options.ssl_cert) if options.http_proxy: args.append('--http-proxy=' + options.http_proxy) + if options.httpd_version: + args.append('--httpd-version=' + options.httpd_version) result, stdout_lines, stderr_lines = spawn_process(command, 0, False, None, *args) @@ -1600,6 +1626,12 @@ class TestRunner: sandbox.cleanup_test_paths() return exit_code +def is_httpd_authz_provider_enabled(): + if is_ra_type_dav(): + v = options.httpd_version.split('.') + return (v[0] == '2' and int(v[1]) >= 3) or int(v[0]) > 2 + return None + ###################################################################### # Main testing functions @@ -1780,6 +1812,8 @@ def _create_parser(): help='Path to SSL server certificate.') parser.add_option('--http-proxy', action='store', help='Use the HTTP Proxy at hostname:port.') + parser.add_option('--httpd-version', action='store', + help='Assume HTTPD is this version.') parser.add_option('--tools-bin', action='store', dest='tools_bin', help='Use the svn tools installed in this path') Index: win-tests.py =================================================================== --- a/win-tests.py (revision 1691883) +++ b/win-tests.py (working copy) @@ -481,6 +481,7 @@ class Httpd: self.httpd_config = os.path.join(self.root, 'httpd.conf') self.httpd_users = os.path.join(self.root, 'users') self.httpd_mime_types = os.path.join(self.root, 'mime.types') + self.httpd_groups = os.path.join(self.root, 'groups') self.abs_builddir = abs_builddir self.abs_objdir = abs_objdir self.service_name = 'svn-test-httpd-' + str(httpd_port) @@ -494,6 +495,7 @@ class Httpd: create_target_dir(self.root_dir) self._create_users_file() + self._create_groups_file() self._create_mime_types_file() self._create_dontdothat_file() @@ -540,6 +542,8 @@ class Httpd: if self.httpd_ver >= 2.2: fp.write(self._sys_module('auth_basic_module', 'mod_auth_basic.so')) fp.write(self._sys_module('authn_file_module', 'mod_authn_file.so')) + fp.write(self._sys_module('authz_groupfile_module', 'mod_authz_groupfile.so')) + fp.write(self._sys_module('authz_host_module', 'mod_authz_host.so')) else: fp.write(self._sys_module('auth_module', 'mod_auth.so')) fp.write(self._sys_module('alias_module', 'mod_alias.so')) @@ -562,6 +566,7 @@ class Httpd: # Define two locations for repositories fp.write(self._svn_repo('repositories')) fp.write(self._svn_repo('local_tmp')) + fp.write(self._svn_authz_repo()) # And two redirects for the redirect tests fp.write('RedirectMatch permanent ^/svn-test-work/repositories/' @@ -592,7 +597,18 @@ class Httpd: 'jrandom', 'rayjandom']) os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp', self.httpd_users, 'jconstant', 'rayjandom']) + os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp', self.httpd_users, + 'JRANDOM', 'rayjandom']) + os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp', self.httpd_users, + 'JCONSTANT', 'rayjandom']) + def _create_groups_file(self): + "Create groups for mod_authz_svn tests" + fp = open(self.httpd_groups, 'w') + fp.write('random: jrandom\n') + fp.write('constant: jconstant\n') + fp.close() + def _create_mime_types_file(self): "Create empty mime.types file" fp = open(self.httpd_mime_types, 'w') @@ -652,6 +668,153 @@ class Httpd: ' DontDoThatConfigFile ' + self._quote(self.dontdothat_file) + '\n' \ '\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: