You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by iv...@apache.org on 2013/01/25 10:59:31 UTC

svn commit: r1438407 - in /subversion/trunk: subversion/include/ subversion/libsvn_repos/ subversion/mod_authz_svn/ subversion/svnserve/ subversion/tests/cmdline/ subversion/tests/cmdline/svntest/ subversion/tests/libsvn_repos/ tools/server-side/

Author: ivan
Date: Fri Jan 25 09:59:30 2013
New Revision: 1438407

URL: http://svn.apache.org/viewvc?rev=1438407&view=rev
Log:
Introduce AuthzSVNGroupsFile option to allow Subversion configurations with 
groups stored in a separate file.

* subversion/include/svn_config.h
  (SVN_CONFIG_OPTION_GROUPS_DB): New define.

* subversion/include/svn_repos.h
  (SVN_REPOS__CONF_GROUPS): New define.
  (svn_repos_authz_read2): New optional 'groups_path' parameter.
  (svn_repos_authz_parse): New optional 'groups_stream' parameter.

* subversion/libsvn_repos/authz.c
  (authz_copy_group): Introduce a callback for groups copying.
  (authz_copy_groups): Introduce a helper routine to copy groups from a
    specified config to the authz structure. Report error if the
    destination authz already contains group definitions.
  (svn_repos__authz_read): Support the 'groups_path' parameter. If it is set,
    parse the corresponding groups config and copy the groups to the
    resulting authz structure using authz_copy_groups.
  (svn_repos_authz_read2): New optional 'groups_path' parameter.
  (svn_repos_authz_read): Support the 'groups_stream' parameter. If it is
    set, parse the corresponding groups config stream and copy the groups
    to the resulting authz structure.

* subversion/libsvn_repos/deprecated.c
  (svn_repos_authz_read): Pass NULL as the 'groups_path' parameter when
    calling svn_repos__authz_read.

* subversion/libsvn_repos/repos.h
  (svn_repos__authz_read): New optional 'groups_path' parameter.

* subversion/libsvn_repos/repos.с
  (create_conf): Explain the purpose of the new groups-db option.

* subversion/mod_authz_svn/mod_authz_svn.c
  (authz_svn_config_rec): Add the 'groups_file' config member.
  (AuthzSVNGroupsFile_cmd): Introduce a function to canonicalize the groups
    file config value.
  (authz_svn_cmds): Add the AuthzSVNGroupsFile option.
  (get_access_conf): Log the path to the groups file if it is set. Pass the
    groups file path to svn_repos_authz_read2.

* subversion/svnserve/serve.c
  (canonicalize_access_file): New function. Factored out from
    load_authz_config to be reused for the groups file.
  (load_authz_config): Retrieve the SVN_CONFIG_OPTION_GROUPS_DB from the
    svnserve config. Canonicalize this value if it is present and pass
    it to svn_repos_authz_read2 when loading the authz configuration.

* tools/server-side/svnauthz.c
  (svnauthz_opt_state): Add the 'groups_file' member.
  (svnauthz__cmdline_options_t): Add the svnauthz__groups_file enum member.
  (options_table): Add the --groups-file option and short description for it.
  (cmd_table): Update the documentation to reflect the added
    svnauthz__groups_file option.
  (read_file_contents): New function. Factored out from get_authz_from_txn to
    be reused for the groups file contents.
  (get_authz_from_txn): Use read_file_contents for both authz and groups
    files. Pass the resulting contents to svn_repos_authz_parse.
  (get_authz): Pass the groups file from options to get_authz_from_txn
    or svn_repos_authz_read2 depending on whether transaction option is set.
  (canonicalize_access_file): New function. Factored out from
    sub_main to be reused for the groups file.
  (sub_main): Grab the groups file option from the command line, canonicalize
    it if it present and pass it further as a part of the opt_state.

* subversion/tests/libsvn_repos/repos-test.c
  (authz-get-handle, in_repo_authz): Pass NULL as the 'groups_path' parameter
    when calling svn_repos_authz_read2. Pass NULL as the 'groups_stream'
    parameter when calling svn_repos_authz_parse.
  (authz_groups_get_handle): Introduce the helper routine for tests similiar
    to authz_get_handle but supporting a separate groups file.
  (groups_authz, in_repo_groups_authz): Add.

* subversion/tests/cmdline/authz_tests.py
  (authz_svnserve_groups): Add the access test for svnserve configured with
    a separate groups file.
  (test_list): Add a reference to the new test.

* subversion/tests/cmdline/svntest/main.py
  (write_restrictive_svnserve_conf_with_groups): New method. Creates a
    default restrictive svnserve configuration with a separate groups file.
  (write_groups_file): Introduce a helper method to write the groups file
    in tests.

* subversion/tests/cmdline/svntest/sandbox.py
  (_set_name): Store the default path to the groups file in the
    'Sandbox.groups_file' variable.

* subversion/mod_authz_svn/INSTALL
  (II.1): Describe usage of AuthzSVNGroupsFile directive.

Patch by: Evgeny Kotkov <evgeny.kotkov{_AT_}visualsvn.com>

Modified:
    subversion/trunk/subversion/include/svn_config.h
    subversion/trunk/subversion/include/svn_repos.h
    subversion/trunk/subversion/libsvn_repos/authz.c
    subversion/trunk/subversion/libsvn_repos/deprecated.c
    subversion/trunk/subversion/libsvn_repos/repos.c
    subversion/trunk/subversion/libsvn_repos/repos.h
    subversion/trunk/subversion/mod_authz_svn/INSTALL
    subversion/trunk/subversion/mod_authz_svn/mod_authz_svn.c
    subversion/trunk/subversion/svnserve/serve.c
    subversion/trunk/subversion/tests/cmdline/authz_tests.py
    subversion/trunk/subversion/tests/cmdline/svntest/main.py
    subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py
    subversion/trunk/subversion/tests/libsvn_repos/repos-test.c
    subversion/trunk/tools/server-side/svnauthz.c

Modified: subversion/trunk/subversion/include/svn_config.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_config.h?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_config.h (original)
+++ subversion/trunk/subversion/include/svn_config.h Fri Jan 25 09:59:30 2013
@@ -149,6 +149,7 @@ typedef struct svn_config_t svn_config_t
 #define SVN_CONFIG_OPTION_USE_SASL                  "use-sasl"
 #define SVN_CONFIG_OPTION_MIN_SSF                   "min-encryption"
 #define SVN_CONFIG_OPTION_MAX_SSF                   "max-encryption"
+#define SVN_CONFIG_OPTION_GROUPS_DB                 "groups-db"
 
 /* For repository password database */
 #define SVN_CONFIG_SECTION_USERS                "users"

Modified: subversion/trunk/subversion/include/svn_repos.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_repos.h?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_repos.h (original)
+++ subversion/trunk/subversion/include/svn_repos.h Fri Jan 25 09:59:30 2013
@@ -3171,19 +3171,24 @@ svn_repos_authz_read(svn_authz_t **authz
  * url, an absolute file url, or a registry path) into @a *authz_p,
  * allocated in @a pool.
  *
- * If @a path is not a valid authz rule file, then return 
+ * If @a groups_path (a file, repos relative url, an absolute file url,
+ * or a registry path) is set, use the global groups parsed from it.
+ *
+ * If @a path or @a groups_path is not a valid authz rule file, then return
  * #SVN_ERR_AUTHZ_INVALID_CONFIG.  The contents of @a *authz_p is then
- * undefined.  If @a must_exist is TRUE, a missing authz file is also
- * an error.
+ * undefined.  If @a must_exist is TRUE, a missing authz or groups file
+ * is also an error.
  *
  * If @a path is a repos relative URL then @a repos_root must be set to
  * the root of the repository the authz configuration will be used with.
+ * The same applies to @a groups_path if it is being used.
  *
  * @since New in 1.8
  */
 svn_error_t *
 svn_repos_authz_read2(svn_authz_t **authz_p,
                       const char *path,
+                      const char *groups_path,
                       svn_boolean_t must_exist,
                       const char *repos_root,
                       apr_pool_t *pool);
@@ -3193,11 +3198,14 @@ svn_repos_authz_read2(svn_authz_t **auth
  * Read authz configuration data from @a stream into @a *authz_p,
  * allocated in @a pool.
  *
+ * If @a groups_stream is set, use the global groups parsed from it.
+ *
  * @since New in 1.8
  */
 svn_error_t *
 svn_repos_authz_parse(svn_authz_t **authz_p,
                       svn_stream_t *stream, 
+                      svn_stream_t *groups_stream,
                       apr_pool_t *pool);
 
 /**

Modified: subversion/trunk/subversion/libsvn_repos/authz.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_repos/authz.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_repos/authz.c (original)
+++ subversion/trunk/subversion/libsvn_repos/authz.c Fri Jan 25 09:59:30 2013
@@ -919,20 +919,81 @@ authz_retrieve_config(svn_config_t **cfg
   return SVN_NO_ERROR;
 }
 
+
+/* Callback to copy (name, value) group into the "groups" section
+   of another configuration. */
+static svn_boolean_t
+authz_copy_group(const char *name, const char *value,
+                 void *baton, apr_pool_t *pool)
+{
+  svn_config_t *authz_cfg = baton;
+
+  svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
+
+  return TRUE;
+}
+
+/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
+ * If AUTHZ already contains any group definition, report an error.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
+                  apr_pool_t *pool)
+{
+  /* Easy out: we prohibit local groups in the authz file when global
+     groups are being used. */
+  if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
+    {
+      return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                              "Authz file cannot contain any groups "
+                              "when global groups are being used.");
+    }
+
+  svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
+                        authz_copy_group, authz->cfg, pool);
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
-                      svn_boolean_t must_exist, svn_boolean_t accept_urls,
-                      const char *repos_root, apr_pool_t *pool)
+                      const char *groups_path, svn_boolean_t must_exist,
+                      svn_boolean_t accept_urls, const char *repos_root,
+                      apr_pool_t *pool)
 {
   svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
 
-  /* Load the rule file */
+  /* Load the authz file */
   if (accept_urls)
     SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, repos_root,
                                   pool));
   else
     SVN_ERR(svn_config_read2(&authz->cfg, path, must_exist, TRUE, pool));
 
+  if (groups_path)
+    {
+      svn_config_t *groups_cfg;
+      svn_error_t *err;
+
+      /* Load the groups file */
+      if (accept_urls)
+        SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist,
+                                      repos_root, pool));
+      else
+        SVN_ERR(svn_config_read2(&groups_cfg, groups_path, must_exist,
+                                 TRUE, pool));
+
+      /* Copy the groups from groups_cfg into authz. */
+      err = authz_copy_groups(authz, groups_cfg, pool);
+
+      /* Add the paths to the error stack since the authz_copy_groups
+         routine knows nothing about them. */
+      if (err != SVN_NO_ERROR)
+        return svn_error_createf(err->apr_err, err,
+                                 "Error reading authz file '%s' with "
+                                 "groups file '%s':", path, groups_path);
+    }
+
   /* Make sure there are no errors in the configuration. */
   SVN_ERR(authz_validate(authz, pool));
 
@@ -946,23 +1007,33 @@ svn_repos__authz_read(svn_authz_t **auth
 
 svn_error_t *
 svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
-                      svn_boolean_t must_exist, const char *repos_root,
-                      apr_pool_t *pool)
+                      const char *groups_path, svn_boolean_t must_exist,
+                      const char *repos_root, apr_pool_t *pool)
 {
-  return svn_repos__authz_read(authz_p, path, must_exist, TRUE, repos_root,
-                               pool);
+  return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
+                               TRUE, repos_root, pool);
 }
 
 
 svn_error_t *
 svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream, 
-                      apr_pool_t *pool)
+                      svn_stream_t *groups_stream, apr_pool_t *pool)
 {
   svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
 
-  /* Parse the stream */
+  /* Parse the authz stream */
   SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, pool));
 
+  if (groups_stream)
+    {
+      svn_config_t *groups_cfg;
+
+      /* Parse the groups stream */
+      SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, pool));
+
+      SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
+    }
+
   /* Make sure there are no errors in the configuration. */
   SVN_ERR(authz_validate(authz, pool));
 

Modified: subversion/trunk/subversion/libsvn_repos/deprecated.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_repos/deprecated.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_repos/deprecated.c (original)
+++ subversion/trunk/subversion/libsvn_repos/deprecated.c Fri Jan 25 09:59:30 2013
@@ -1013,5 +1013,6 @@ svn_error_t *
 svn_repos_authz_read(svn_authz_t **authz_p, const char *file,
                      svn_boolean_t must_exist, apr_pool_t *pool)
 {
-  return svn_repos__authz_read(authz_p, file, must_exist, FALSE, NULL, pool);
+  return svn_repos__authz_read(authz_p, file, NULL, must_exist,
+                               FALSE, NULL, pool);
 }

Modified: subversion/trunk/subversion/libsvn_repos/repos.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_repos/repos.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_repos/repos.c (original)
+++ subversion/trunk/subversion/libsvn_repos/repos.c Fri Jan 25 09:59:30 2013
@@ -1025,6 +1025,12 @@ create_conf(svn_repos_t *repos, apr_pool
 "### no path-based access control is done."                                  NL
 "### Uncomment the line below to use the default authorization file."        NL
 "# authz-db = " SVN_REPOS__CONF_AUTHZ                                        NL
+"### The groups-db option controls the location of the groups file."         NL
+"### Unless you specify a path starting with a /, the file's location is"    NL
+"### relative to the directory containing this file.  The specified path"    NL
+"### may be a repository relative URL (^/) or an absolute file:// URL to a"  NL
+"### text file in a Subversion repository."                                  NL
+"# groups-db = " SVN_REPOS__CONF_GROUPS                                      NL
 "### This option specifies the authentication realm of the repository."      NL
 "### If two repositories have the same authentication realm, they should"    NL
 "### have the same password database, and vice versa.  The default realm"    NL

Modified: subversion/trunk/subversion/libsvn_repos/repos.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_repos/repos.h?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_repos/repos.h (original)
+++ subversion/trunk/subversion/libsvn_repos/repos.h Fri Jan 25 09:59:30 2013
@@ -95,10 +95,11 @@ extern "C" {
 #define SVN_REPOS__CONF_SVNSERVE_CONF "svnserve.conf"
 
 /* In the svnserve default configuration, these are the suggested
-   locations for the passwd and authz files (in the repository conf
-   directory), and we put example templates there. */
+   locations for the passwd, authz and groups files (in the repository
+   conf directory), and we put example templates there. */
 #define SVN_REPOS__CONF_PASSWD "passwd"
 #define SVN_REPOS__CONF_AUTHZ "authz"
+#define SVN_REPOS__CONF_GROUPS "groups"
 
 /* The Repository object, created by svn_repos_open2() and
    svn_repos_create(). */
@@ -307,22 +308,24 @@ svn_repos__hooks_post_unlock(svn_repos_t
 /*** Authz Functions ***/
 
 /* Read authz configuration data from PATH into *AUTHZ_P, allocated
-   in POOL.
-  
-   PATH may be a file or a registry path and iff ACCEPT_URLS is set
-   it may also be a repos relative url or an absolute file url.  When
+   in POOL.  If GROUPS_PATH is set, use the global groups parsed from it.
+
+   PATH and GROUPS_PATH may be a file or a registry path and iff ACCEPT_URLS
+   is set it may also be a repos relative url or an absolute file url.  When
    ACCEPT_URLS is FALSE REPOS_ROOT can be NULL.
-  
-   If PATH is not a valid authz rule file, then return 
+
+   If PATH or GROUPS_PATH is not a valid authz rule file, then return 
    SVN_AUTHZ_INVALID_CONFIG.  The contents of *AUTHZ_P is then
-   undefined.  If MUST_EXIST is TRUE, a missing authz file is also
-   an error.
-  
+   undefined.  If MUST_EXIST is TRUE, a missing authz or global groups file
+   is also an error.
+
    If PATH is a repos relative URL then REPOS_ROOT must be set to
-   the root of the repository the authz configuration will be used with. */
+   the root of the repository the authz configuration will be used with.
+   The same applies to GROUPS_PATH if it is being used. */
 svn_error_t *
 svn_repos__authz_read(svn_authz_t **authz_p,
                       const char *path,
+                      const char *groups_path,
                       svn_boolean_t must_exist,
                       svn_boolean_t accept_urls,
                       const char *repos_root,

Modified: subversion/trunk/subversion/mod_authz_svn/INSTALL
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/mod_authz_svn/INSTALL?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/mod_authz_svn/INSTALL (original)
+++ subversion/trunk/subversion/mod_authz_svn/INSTALL Fri Jan 25 09:59:30 2013
@@ -186,6 +186,24 @@ II.   Configuration
          The "Require" statement in the previous example is not strictly
          needed, but has been included for clarity.
 
+      H. Example 8: Separate authz and groups files.
+
+         This configuration allows storing the groups separately from the
+         main authz file with the authorization rules.
+
+         <Location /svn>
+           DAV svn
+           SVNParentPath /path/to/reposparent
+
+           AuthType Basic
+           AuthName "Subversion repository"
+           AuthUserFile /path/to/htpasswd/file
+
+           AuthzSVNAccessFile /path/to/access/file
+           AuthzSVNGroupsFile /path/to/groups/file
+
+           Require valid-user
+         </Location>
 
    2. Specifying permissions
 

Modified: subversion/trunk/subversion/mod_authz_svn/mod_authz_svn.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/mod_authz_svn/mod_authz_svn.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/mod_authz_svn/mod_authz_svn.c (original)
+++ subversion/trunk/subversion/mod_authz_svn/mod_authz_svn.c Fri Jan 25 09:59:30 2013
@@ -63,6 +63,7 @@ typedef struct authz_svn_config_rec {
   const char *base_path;
   const char *access_file;
   const char *repo_relative_access_file;
+  const char *groups_file;
   const char *force_username_case;
 } authz_svn_config_rec;
 
@@ -147,6 +148,16 @@ AuthzSVNReposRelativeAccessFile_cmd(cmd_
   return NULL;
 }
 
+static const char *
+AuthzSVNGroupsFile_cmd(cmd_parms *cmd, void *config, const char *arg1)
+{
+  authz_svn_config_rec *conf = config;
+
+  conf->groups_file = canonicalize_access_file(arg1, TRUE, cmd->pool);
+
+  return NULL;
+}
+
 /* Implements the #cmds member of Apache's #module vtable. */
 static const command_rec authz_svn_cmds[] =
 {
@@ -170,6 +181,14 @@ static const command_rec authz_svn_cmds[
                 "file containing permissions of repository paths. Path may "
                 "be an repository relative URL (^/) or absolute file:// URL "
                 "to a text file in a Subversion repository."),
+  AP_INIT_TAKE1("AuthzSVNGroupsFile",
+                AuthzSVNGroupsFile_cmd,
+                NULL,
+                OR_AUTHCFG,
+                "Path to text file containing group definitions for all "
+                "repositories.  Path may be an repository relative URL (^/) "
+                "or absolute file:// URL to a text file in a Subversion "
+                "repository."),
   AP_INIT_FLAG("AuthzSVNAnonymous", ap_set_flag_slot,
                (void *)APR_OFFSETOF(authz_svn_config_rec, anonymous),
                OR_AUTHCFG,
@@ -331,6 +350,12 @@ get_access_conf(request_rec *r, authz_sv
   ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                 "Path to authz file is %s", access_file);
 
+  if (conf->groups_file)
+    {
+      ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                    "Path to groups file is %s", conf->groups_file);
+    }
+
   cache_key = apr_pstrcat(scratch_pool, "mod_authz_svn:",
                           access_file, (char *)NULL);
   apr_pool_userdata_get(&user_data, cache_key, r->connection->pool);
@@ -338,7 +363,8 @@ get_access_conf(request_rec *r, authz_sv
   if (access_conf == NULL)
     {
       svn_err = svn_repos_authz_read2(&access_conf, access_file,
-                                      TRUE, repos_path, r->connection->pool);
+                                      conf->groups_file, TRUE, repos_path,
+                                      r->connection->pool);
       if (svn_err)
         {
           log_svn_error(APLOG_MARK, r,

Modified: subversion/trunk/subversion/svnserve/serve.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/svnserve/serve.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/svnserve/serve.c (original)
+++ subversion/trunk/subversion/svnserve/serve.c Fri Jan 25 09:59:30 2013
@@ -269,35 +269,61 @@ svn_error_t *load_pwdb_config(server_bat
   return SVN_NO_ERROR;
 }
 
+/* Canonicalize ACCESS_FILE based on the type of argument.
+ * SERVER baton is used to convert relative paths to absolute paths
+ * rooted at the server root. */
+static const char *
+canonicalize_access_file(const char *access_file,
+                         server_baton_t *server,
+                         apr_pool_t *pool)
+{
+  if (svn_path_is_url(access_file))
+    {
+      access_file = svn_uri_canonicalize(access_file, pool);
+    }
+  else if (!svn_path_is_repos_relative_url(access_file))
+    {
+      access_file = svn_dirent_internal_style(access_file, pool);
+      access_file = svn_dirent_join(server->base, access_file, pool);
+    }
+
+  /* We don't canonicalize repos relative urls since they get
+   * canonicalized inside svn_repos_authz_read2() when they
+   * are resolved. */
+
+  return access_file;
+}
+
 svn_error_t *load_authz_config(server_baton_t *server,
                                svn_ra_svn_conn_t *conn,
                                const char *repos_root,
                                apr_pool_t *pool)
 {
   const char *authzdb_path;
+  const char *groupsdb_path;
   svn_error_t *err;
 
   /* Read authz configuration. */
   svn_config_get(server->cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
                  SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
+
+  svn_config_get(server->cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
+                 SVN_CONFIG_OPTION_GROUPS_DB, NULL);
+
   if (authzdb_path)
     {
       const char *case_force_val;
 
-      /* Canonicalize and add the base onto the authzdb_path (if needed).
-       * We don't canonicalize repos relative urls since they are
-       * canonicalized when they are resolved in svn_repos_authz_read2(). */
-      if (svn_path_is_url(authzdb_path))
-        {
-          authzdb_path = svn_uri_canonicalize(authzdb_path, pool);
-        }
-      else if (!svn_path_is_repos_relative_url(authzdb_path))
-        {
-          authzdb_path = svn_dirent_internal_style(authzdb_path, pool);
-          authzdb_path = svn_dirent_join(server->base, authzdb_path, pool);
-        }
-      err = svn_repos_authz_read2(&server->authzdb, authzdb_path, TRUE,
-                                  repos_root, pool);
+      /* Canonicalize and add the base onto the authzdb_path (if needed). */
+      authzdb_path = canonicalize_access_file(authzdb_path, server, pool);
+
+      /* Same for the groupsdb_path if it is present. */
+      if (groupsdb_path)
+        groupsdb_path = canonicalize_access_file(groupsdb_path,
+                                                 server, pool);
+
+      err = svn_repos_authz_read2(&server->authzdb, authzdb_path,
+                                  groupsdb_path, TRUE, repos_root, pool);
       if (err)
         {
           log_server_error(err, server, conn, pool);

Modified: subversion/trunk/subversion/tests/cmdline/authz_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/authz_tests.py?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/authz_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/authz_tests.py Fri Jan 25 09:59:30 2013
@@ -1452,6 +1452,69 @@ def remove_subdir_with_authz_and_tc(sbox
                                         None, None, False,
                                         wc_dir)
 
+@SkipUnless(svntest.main.is_ra_type_svn)
+def authz_svnserve_groups(sbox):
+  "authz with configured global groups"
+
+  sbox.build(create_wc = False)
+
+  svntest.main.write_restrictive_svnserve_conf_with_groups(sbox.repo_dir)
+
+  svntest.main.write_authz_file(sbox, { "/A/B" : "@senate = r",
+                                        "/A/D" : "@senate = rw",
+                                        "/A/B/E" : "@senate = " })
+
+  svntest.main.write_groups_file(sbox, { "senate" : "jrandom" })
+
+  root_url = sbox.repo_url
+  A_url = root_url + '/A'
+  B_url = A_url + '/B'
+  E_url = B_url + '/E'
+  F_url = B_url + '/F'
+  D_url = A_url + '/D'
+  G_url = D_url + '/G'
+  lambda_url = B_url + '/lambda'
+  pi_url = G_url + '/pi'
+  alpha_url = E_url + '/alpha'
+
+  expected_err = ".*svn: E170001: Authorization failed.*"
+
+  # read a remote file
+  svntest.actions.run_and_verify_svn(None, ["This is the file 'lambda'.\n"],
+                                     [], 'cat',
+                                     lambda_url)
+
+  # read a remote file
+  svntest.actions.run_and_verify_svn(None, ["This is the file 'pi'.\n"],
+                                     [], 'cat',
+                                     pi_url)
+
+  # read a remote file, unreadable: should fail
+  svntest.actions.run_and_verify_svn(None,
+                                     None, expected_err,
+                                     'cat',
+                                     alpha_url)
+
+  # copy a remote file, source is unreadable: should fail
+  svntest.actions.run_and_verify_svn(None,
+                                     None, expected_err,
+                                     'cp',
+                                     '-m', 'logmsg',
+                                     alpha_url, B_url)
+
+  # copy a remote folder
+  svntest.actions.run_and_verify_svn(None, None, [],
+                                     'cp',
+                                     '-m', 'logmsg',
+                                     F_url, D_url)
+
+  # copy a remote folder, source is unreadable: should fail
+  svntest.actions.run_and_verify_svn(None,
+                                     None, expected_err,
+                                     'cp',
+                                     '-m', 'logmsg',
+                                     E_url, D_url)
+
 ########################################################################
 # Run the tests
 
@@ -1481,7 +1544,8 @@ test_list = [ None,
               wc_delete,
               wc_commit_error_handling,
               upgrade_absent,
-              remove_subdir_with_authz_and_tc
+              remove_subdir_with_authz_and_tc,
+              authz_svnserve_groups
              ]
 serial_only = True
 

Modified: subversion/trunk/subversion/tests/cmdline/svntest/main.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svntest/main.py?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/svntest/main.py (original)
+++ subversion/trunk/subversion/tests/cmdline/svntest/main.py Fri Jan 25 09:59:30 2013
@@ -1065,6 +1065,19 @@ def write_restrictive_svnserve_conf(repo
     fp.write("password-db = passwd\n")
   fp.close()
 
+def write_restrictive_svnserve_conf_with_groups(repo_dir,
+                                                anon_access="none"):
+  "Create a restrictive configuration with groups stored in a separate file."
+
+  fp = open(get_svnserve_conf_file_path(repo_dir), 'w')
+  fp.write("[general]\nanon-access = %s\nauth-access = write\n"
+           "authz-db = authz\ngroups-db = groups\n" % anon_access)
+  if options.enable_sasl:
+    fp.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
+  else:
+    fp.write("password-db = passwd\n")
+  fp.close()
+
 # Warning: because mod_dav_svn uses one shared authz file for all
 # repositories, you *cannot* use write_authz_file in any test that
 # might be run in parallel.
@@ -1100,6 +1113,18 @@ an appropriate list of mappings.
     fp.write("[%s%s]\n%s\n" % (prefix, p, r))
   fp.close()
 
+# See the warning about parallel test execution in write_authz_file
+# method description.
+def write_groups_file(sbox, groups):
+  """Write a groups file to SBOX, appropriate for the RA method used,
+with group contents set to GROUPS."""
+  fp = open(sbox.groups_file, 'w')
+  fp.write("[groups]\n")
+  if groups:
+    for p, r in groups.items():
+      fp.write("%s = %s\n" % (p, r))
+  fp.close()
+
 def use_editor(func):
   os.environ['SVN_EDITOR'] = svneditor_script
   os.environ['SVN_MERGE'] = svneditor_script

Modified: subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py (original)
+++ subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py Fri Jan 25 09:59:30 2013
@@ -78,12 +78,14 @@ class Sandbox:
       tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name)
       open(tmp_authz_file, 'w').write("[/]\n* = rw\n")
       shutil.move(tmp_authz_file, self.authz_file)
+      self.groups_file = os.path.join(svntest.main.work_dir, "groups")
 
     # For svnserve tests we have a per-repository authz file, and it
     # doesn't need to be there in order for things to work, so we don't
     # have any default contents.
     elif self.repo_url.startswith("svn"):
       self.authz_file = os.path.join(self.repo_dir, "conf", "authz")
+      self.groups_file = os.path.join(self.repo_dir, "conf", "groups")
 
   def clone_dependent(self, copy_wc=False):
     """A convenience method for creating a near-duplicate of this

Modified: subversion/trunk/subversion/tests/libsvn_repos/repos-test.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_repos/repos-test.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_repos/repos-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_repos/repos-test.c Fri Jan 25 09:59:30 2013
@@ -1157,7 +1157,7 @@ authz_get_handle(svn_authz_t **authz_p, 
       SVN_ERR_W(svn_stream_puts(stream, authz_contents),
                 "Writing authz contents to stream");
 
-      SVN_ERR_W(svn_repos_authz_parse(authz_p, stream, pool),
+      SVN_ERR_W(svn_repos_authz_parse(authz_p, stream, NULL, pool),
                 "Parsing the authz contents");
 
       SVN_ERR_W(svn_stream_close(stream),
@@ -1451,24 +1451,25 @@ in_repo_authz(const svn_test_opts_t *opt
 
   /* repos relative URL */
   repos_root = svn_repos_path(repos, pool);
-  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/authz", TRUE, repos_root,
-                                pool));
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/authz", NULL, TRUE,
+                                repos_root, pool));
   SVN_ERR(authz_check_access(authz_cfg, test_set, pool));
 
   /* absolute file URL, repos_root is NULL to validate the contract that it
    * is not needed except when a repos relative URL is passed. */
   SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_url, repos_root, pool));
   authz_url = apr_pstrcat(pool, repos_url, "/authz", (char *)NULL);
-  SVN_ERR(svn_repos_authz_read2(&authz_cfg, authz_url, TRUE, NULL, pool));
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, authz_url, NULL, TRUE,
+                                NULL, pool));
   SVN_ERR(authz_check_access(authz_cfg, test_set, pool));
   
   /* Non-existant path in the repo with must_exist set to FALSE */ 
-  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/A/authz", FALSE, repos_root,
-                                pool));
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/A/authz", NULL, FALSE,
+                                repos_root, pool));
 
   /* Non-existant path in the repo with must_exist set to TRUE */ 
-  err = svn_repos_authz_read2(&authz_cfg, "^/A/authz", TRUE, repos_root,
-                              pool);
+  err = svn_repos_authz_read2(&authz_cfg, "^/A/authz", NULL, TRUE,
+                              repos_root, pool);
   if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
     return svn_error_createf(SVN_ERR_TEST_FAILED, err,
                              "Got %s error instead of expected "
@@ -1478,7 +1479,7 @@ in_repo_authz(const svn_test_opts_t *opt
 
   /* http:// URL which is unsupported */
   err = svn_repos_authz_read2(&authz_cfg, "http://example.com/repo/authz",
-                              TRUE, repos_root, pool);
+                              NULL, TRUE, repos_root, pool);
   if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL)
     return svn_error_createf(SVN_ERR_TEST_FAILED, err,
                              "Got %s error instead of expected "
@@ -1488,6 +1489,149 @@ in_repo_authz(const svn_test_opts_t *opt
 
   /* svn:// URL which is unsupported */
   err = svn_repos_authz_read2(&authz_cfg, "svn://example.com/repo/authz",
+                              NULL, TRUE, repos_root, pool);
+  if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_RA_ILLEGAL_URL",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Test in-repo authz with global groups. */
+static svn_error_t *
+in_repo_groups_authz(const svn_test_opts_t *opts,
+                     apr_pool_t *pool)
+{
+  svn_repos_t *repos;
+  svn_fs_t *fs;
+  svn_fs_txn_t *txn;
+  svn_fs_root_t *txn_root;
+  svn_revnum_t youngest_rev;
+  svn_authz_t *authz_cfg;
+  const char *groups_contents;
+  const char *authz_contents;
+  const char *repos_root;
+  const char *repos_url;
+  const char *groups_url;
+  const char *authz_url;
+  svn_error_t *err;
+  struct check_access_tests test_set[] = {
+    /* reads */
+    { "/A", NULL, NULL, svn_authz_read, FALSE },
+    { "/A", NULL, "plato", svn_authz_read, TRUE },
+    { "/A", NULL, "socrates", svn_authz_read, TRUE },
+    { "/A", NULL, "solon", svn_authz_read, TRUE },
+    { "/A", NULL, "ephialtes", svn_authz_read, TRUE },
+    /* writes */
+    { "/A", NULL, NULL, svn_authz_write, FALSE },
+    { "/A", NULL, "plato", svn_authz_write, FALSE },
+    { "/A", NULL, "socrates", svn_authz_write, FALSE },
+    { "/A", NULL, "solon", svn_authz_write, TRUE },
+    { "/A", NULL, "ephialtes", svn_authz_write, TRUE },
+    /* Sentinel */
+    { NULL, NULL, NULL, svn_authz_none, FALSE }
+  };
+
+  /* Test plan:
+   * 1. Create an authz file, a global groups file and an empty authz file,
+   *    put all these files in the repository.  The empty authz file is
+   *    required to perform the non-existent path checks (4-7) --
+   *    otherwise we would get the authz validation error due to undefined
+   *    groups.
+   * 2. Verify that the groups file can be read with an relative URL.
+   * 3. Verify that the groups file can be read with an absolute URL.
+   * 4. Verify that non-existent groups file path does not error out when
+   *    must_exist is FALSE.
+   * 5. Same as (4), but when both authz and groups file paths do
+   *    not exist.
+   * 6. Verify that non-existent path for the groups file does error out when
+   *    must_exist is TRUE.
+   * 7. Verify that an http:// URL produces an error.
+   * 8. Verify that an svn:// URL produces an error.
+   */
+
+  /* What we'll put in the authz and groups files, it's simple since
+   * we're not testing the parsing, just that we got what we expected. */
+
+  groups_contents =
+    "[groups]"                                                               NL
+    "philosophers = plato, socrates"                                         NL
+    "senate = solon, ephialtes"                                              NL
+    ""                                                                       NL;
+
+  authz_contents =
+    "[/]"                                                                    NL
+    "@senate = rw"                                                           NL
+    "@philosophers = r"                                                      NL
+    ""                                                                       NL;
+
+  /* Create a filesystem and repository. */
+  SVN_ERR(svn_test__create_repos(&repos,
+                                 "test-repo-in-repo-global-groups-authz",
+                                 opts, pool));
+  fs = svn_repos_fs(repos);
+
+  /* Commit the authz, empty authz and groups files to the repo. */
+  SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
+  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
+  SVN_ERR(svn_fs_make_file(txn_root, "groups", pool));
+  SVN_ERR(svn_fs_make_file(txn_root, "authz", pool));
+  SVN_ERR(svn_fs_make_file(txn_root, "empty-authz", pool));
+  SVN_ERR(svn_test__set_file_contents(txn_root, "groups",
+                                      groups_contents, pool));
+  SVN_ERR(svn_test__set_file_contents(txn_root, "authz",
+                                      authz_contents, pool));
+  SVN_ERR(svn_test__set_file_contents(txn_root, "empty-authz", "", pool));
+  SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
+  SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
+
+  /* repos relative URLs */
+  repos_root = svn_repos_path(repos, pool);
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/authz", "^/groups",
+                                TRUE, repos_root, pool));
+  SVN_ERR(authz_check_access(authz_cfg, test_set, pool));
+
+  /* absolute file URLs, repos_root is NULL to validate the contract that it
+   * is not needed except when a repos relative URLs are passed. */
+  SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_url, repos_root, pool));
+  authz_url = apr_pstrcat(pool, repos_url, "/authz", (char *)NULL);
+  groups_url = apr_pstrcat(pool, repos_url, "/groups", (char *)NULL);
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, authz_url, groups_url,
+                                TRUE, NULL, pool));
+  SVN_ERR(authz_check_access(authz_cfg, test_set, pool));
+
+  /* Non-existent path for the groups file with must_exist
+   * set to TRUE */
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/empty-authz",
+                                "^/A/groups", FALSE,
+                                repos_root, pool));
+
+  /* Non-existent paths for both the authz and the groups files
+   * with must_exist set to TRUE */
+  SVN_ERR(svn_repos_authz_read2(&authz_cfg, "^/A/authz",
+                                "^/A/groups", FALSE,
+                                repos_root, pool));
+
+  /* Non-existent path for the groups file with must_exist
+   * set to TRUE */
+  err = svn_repos_authz_read2(&authz_cfg, "^/empty-authz",
+                              "^/A/groups", TRUE,
+                              repos_root, pool);
+  if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_AUTHZ_INVALID_CONFIG",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+  /* http:// URL which is unsupported */
+  err = svn_repos_authz_read2(&authz_cfg, "^/empty-authz",
+                              "http://example.com/repo/groups",
                               TRUE, repos_root, pool);
   if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL)
     return svn_error_createf(SVN_ERR_TEST_FAILED, err,
@@ -1496,10 +1640,264 @@ in_repo_authz(const svn_test_opts_t *opt
                              err ? "unexpected" : "no");
   svn_error_clear(err);
 
+  /* svn:// URL which is unsupported */
+  err = svn_repos_authz_read2(&authz_cfg, "^/empty-authz",
+                              "http://example.com/repo/groups",
+                              TRUE, repos_root, pool);
+  if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_RA_ILLEGAL_URL",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Helper for the groups_authz test.  Set *AUTHZ_P to a representation of
+   AUTHZ_CONTENTS in conjuction with GROUPS_CONTENTS, using POOL for
+   temporary allocation.  If DISK is TRUE then write the contents to
+   temporary files and use svn_repos_authz_read2() to get the data if FALSE
+   write the data to a buffered stream and use svn_repos_authz_parse(). */
+static svn_error_t *
+authz_groups_get_handle(svn_authz_t **authz_p,
+                        const char *authz_contents,
+                        const char *groups_contents,
+                        svn_boolean_t disk,
+                        apr_pool_t *pool)
+{
+  if (disk)
+    {
+      const char *authz_file_path;
+      const char *groups_file_path;
+
+      /* Create temporary files. */
+      SVN_ERR_W(svn_io_write_unique(&authz_file_path, NULL,
+                                    authz_contents,
+                                    strlen(authz_contents),
+                                    svn_io_file_del_on_pool_cleanup, pool),
+                "Writing temporary authz file");
+      SVN_ERR_W(svn_io_write_unique(&groups_file_path, NULL,
+                                    groups_contents,
+                                    strlen(groups_contents),
+                                    svn_io_file_del_on_pool_cleanup, pool),
+                "Writing temporary groups file");
+
+      /* Read the authz configuration back and start testing. */
+      SVN_ERR_W(svn_repos_authz_read2(authz_p, authz_file_path,
+                                      groups_file_path, TRUE, NULL, pool),
+                "Opening test authz and groups files");
+
+      /* Done with the files. */
+      SVN_ERR_W(svn_io_remove_file(authz_file_path, pool),
+                "Removing test authz file");
+      SVN_ERR_W(svn_io_remove_file(groups_file_path, pool),
+                "Removing test groups file");
+    }
+  else
+    {
+      svn_stream_t *stream;
+      svn_stream_t *groups_stream;
+
+      /* Create the streams. */
+      stream = svn_stream_buffered(pool);
+      groups_stream = svn_stream_buffered(pool);
+
+      SVN_ERR_W(svn_stream_puts(stream, authz_contents),
+                "Writing authz contents to stream");
+      SVN_ERR_W(svn_stream_puts(groups_stream, groups_contents),
+                "Writing groups contents to stream");
+
+      /* Read the authz configuration from the streams and start testing. */
+      SVN_ERR_W(svn_repos_authz_parse(authz_p, stream, groups_stream, pool),
+                "Parsing the authz and groups contents");
+
+      /* Done with the streams. */
+      SVN_ERR_W(svn_stream_close(stream),
+                "Closing the authz stream");
+      SVN_ERR_W(svn_stream_close(groups_stream),
+                "Closing the groups stream");
+    }
 
   return SVN_NO_ERROR;
 }
 
+/* Test authz with global groups. */
+static svn_error_t *
+groups_authz(const svn_test_opts_t *opts,
+             apr_pool_t *pool)
+{
+  svn_authz_t *authz_cfg;
+  const char *authz_contents;
+  const char *groups_contents;
+  svn_error_t *err;
+
+  struct check_access_tests test_set1[] = {
+    /* reads */
+    { "/A", "greek", NULL, svn_authz_read, FALSE },
+    { "/A", "greek", "plato", svn_authz_read, TRUE },
+    { "/A", "greek", "demetrius", svn_authz_read, TRUE },
+    { "/A", "greek", "galenos", svn_authz_read, TRUE },
+    { "/A", "greek", "pamphilos", svn_authz_read, FALSE },
+    /* writes */
+    { "/A", "greek", NULL, svn_authz_write, FALSE },
+    { "/A", "greek", "plato", svn_authz_write, TRUE },
+    { "/A", "greek", "demetrius", svn_authz_write, FALSE },
+    { "/A", "greek", "galenos", svn_authz_write, FALSE },
+    { "/A", "greek", "pamphilos", svn_authz_write, FALSE },
+    /* Sentinel */
+    { NULL, NULL, NULL, svn_authz_none, FALSE }
+  };
+
+  struct check_access_tests test_set2[] = {
+    /* reads */
+    { "/A", "greek", NULL, svn_authz_read, FALSE },
+    { "/A", "greek", "socrates", svn_authz_read, FALSE },
+    { "/B", "greek", NULL, svn_authz_read, FALSE},
+    { "/B", "greek", "socrates", svn_authz_read, TRUE },
+    /* writes */
+    { "/A", "greek", NULL, svn_authz_write, FALSE },
+    { "/A", "greek", "socrates", svn_authz_write, FALSE },
+    { "/B", "greek", NULL, svn_authz_write, FALSE},
+    { "/B", "greek", "socrates", svn_authz_write, TRUE },
+    /* Sentinel */
+    { NULL, NULL, NULL, svn_authz_none, FALSE }
+  };
+
+  /* Test plan:
+   * 1. Ensure that a simple setup with global groups and access rights in
+   *    two separate files works as expected.
+   * 2. Verify that access rights written in the global groups file are
+   *    discarded and affect nothing in authorization terms.
+   * 3. Verify that local groups in the authz file are prohibited in
+   *    conjuction with global groups (and that a configuration error is
+   *    reported in this scenario).
+   * 4. Ensure that group cycles in the global groups file are reported.
+   *
+   * All checks are performed twice -- for the configurations stored on disk
+   * and in memory.  See authz_groups_get_handle.
+   */
+
+  groups_contents =
+    "[groups]"                                                               NL
+    "slaves = pamphilos,@gladiators"                                         NL
+    "gladiators = demetrius,galenos"                                         NL
+    "philosophers = plato"                                                   NL
+    ""                                                                       NL;
+
+  authz_contents =
+    "[greek:/A]"                                                             NL
+    "@slaves = "                                                             NL
+    "@gladiators = r"                                                        NL
+    "@philosophers = rw"                                                     NL
+    ""                                                                       NL;
+
+  SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents,
+                                  groups_contents, TRUE, pool));
+
+  SVN_ERR(authz_check_access(authz_cfg, test_set1, pool));
+
+  SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents,
+                                  groups_contents, FALSE, pool));
+
+  SVN_ERR(authz_check_access(authz_cfg, test_set1, pool));
+
+  /* Access rights in the global groups file are discarded. */
+  groups_contents =
+    "[groups]"                                                               NL
+    "philosophers = socrates"                                                NL
+    ""                                                                       NL
+    "[greek:/A]"                                                             NL
+    "@philosophers = rw"                                                     NL
+    ""                                                                       NL;
+
+  authz_contents =
+    "[greek:/B]"                                                             NL
+    "@philosophers = rw"                                                     NL
+    ""                                                                       NL;
+
+  SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents,
+                                  groups_contents, TRUE, pool));
+
+  SVN_ERR(authz_check_access(authz_cfg, test_set2, pool));
+
+  SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents,
+                                  groups_contents, FALSE, pool));
+
+  SVN_ERR(authz_check_access(authz_cfg, test_set2, pool));
+
+  /* Local groups cannot be used in conjuction with global groups. */
+  groups_contents =
+    "[groups]"                                                               NL
+    "slaves = maximus"                                                       NL
+    ""                                                                       NL;
+
+  authz_contents =
+    "[greek:/A]"                                                             NL
+    "@slaves = "                                                             NL
+    "@kings = rw"                                                            NL
+    ""                                                                       NL
+    "[groups]"                                                               NL
+    /* That's an epic story of the slave who tried to become a king. */
+    "kings = maximus"                                                        NL
+    ""                                                                       NL;
+
+  err = authz_groups_get_handle(&authz_cfg, authz_contents,
+                                groups_contents, TRUE, pool);
+
+  if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_AUTHZ_INVALID_CONFIG",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+  err = authz_groups_get_handle(&authz_cfg, authz_contents,
+                                groups_contents, FALSE, pool);
+
+  if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_AUTHZ_INVALID_CONFIG",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+  /* Ensure that group cycles are reported. */
+  groups_contents =
+    "[groups]"                                                               NL
+    "slaves = cooks,scribes,@gladiators"                                     NL
+    "gladiators = equites,thraces,@slaves"                                   NL
+    ""                                                                       NL;
+
+  authz_contents =
+    "[greek:/A]"                                                             NL
+    "@slaves = r"                                                            NL
+    ""                                                                       NL;
+
+  err = authz_groups_get_handle(&authz_cfg, authz_contents,
+                                groups_contents, TRUE, pool);
+
+  if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_AUTHZ_INVALID_CONFIG",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+  err = authz_groups_get_handle(&authz_cfg, authz_contents,
+                                groups_contents, FALSE, pool);
+
+  if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, err,
+                             "Got %s error instead of expected "
+                             "SVN_ERR_AUTHZ_INVALID_CONFIG",
+                             err ? "unexpected" : "no");
+  svn_error_clear(err);
+
+  return SVN_NO_ERROR;
+}
 
 /* Callback for the commit editor tests that relays requests to
    authz. */
@@ -2775,6 +3173,10 @@ struct svn_test_descriptor_t test_funcs[
                    "test authz access control"),
     SVN_TEST_OPTS_PASS(in_repo_authz,
                        "test authz stored in the repo"),
+    SVN_TEST_OPTS_PASS(in_repo_groups_authz,
+                       "test authz and global groups stored in the repo"),
+    SVN_TEST_OPTS_PASS(groups_authz,
+                       "test authz with global groups"),
     SVN_TEST_OPTS_PASS(commit_editor_authz,
                        "test authz in the commit editor"),
     SVN_TEST_OPTS_PASS(commit_continue_txn,

Modified: subversion/trunk/tools/server-side/svnauthz.c
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnauthz.c?rev=1438407&r1=1438406&r2=1438407&view=diff
==============================================================================
--- subversion/trunk/tools/server-side/svnauthz.c (original)
+++ subversion/trunk/tools/server-side/svnauthz.c Fri Jan 25 09:59:30 2013
@@ -39,7 +39,8 @@ enum svnauthz__cmdline_options_t
   svnauthz__username,
   svnauthz__path,
   svnauthz__repos,
-  svnauthz__is
+  svnauthz__is,
+  svnauthz__groups_file
 };
 
 /* Option codes and descriptions.
@@ -66,6 +67,7 @@ static const apr_getopt_option_t options
      "                             "
      "   no    no access\n")
   },
+  {"groups-file", svnauthz__groups_file, 1, ("path to the global groups file")},
   {0, 0, 0, 0}
 };
 
@@ -74,6 +76,7 @@ struct svnauthz_opt_state
   svn_boolean_t help;
   svn_boolean_t version;
   const char *authz_file;
+  const char *groups_file;
   const char *username;
   const char *fspath;
   const char *repos_name;
@@ -122,8 +125,9 @@ static const svn_opt_subcommand_desc2_t 
    {'t'} },
   {"accessof", subcommand_accessof, {0} /* no aliases */,
    ("Print or test the permissions set by an authz file for a specific circumstance.\n"
-    "usage: 1. svnauthz accessof [--username USER] TARGET\n"
-    "       2. svnauthz accessof [--username USER] -t TXN REPOS_PATH FILE_PATH\n\n" 
+    "usage: 1. svnauthz accessof [--username USER] [--groups-file GROUPS_FILE] TARGET\n"
+    "       2. svnauthz accessof [--username USER] [--groups-file GROUPS_FILE] \\\n"
+    "                            -t TXN REPOS_PATH FILE_PATH\n\n"
     "  1. Prints the access of USER based on TARGET.\n"
     "     TARGET can be a path to a file or an absolute file:// URL to an authz\n"
     "     file in a repository, but cannot be a repository relative URL (^/).\n\n"
@@ -131,7 +135,8 @@ static const svn_opt_subcommand_desc2_t 
     "     transaction TXN in the repository at REPOS_PATH.\n\n"
     "  If the --username argument is omitted then access of an anonymous user\n"
     "  will be printed.  If --path argument is omitted prints if any access\n"
-    "  to the repo is allowed.\n\n"
+    "  to the repo is allowed.  If --groups-file is specified, the groups from\n"
+    "  GROUPS_FILE will be used.\n\n"
     "Outputs one of the following:\n"
     "     rw    write access (which also implies read)\n"
     "      r    read access\n"
@@ -142,7 +147,8 @@ static const svn_opt_subcommand_desc2_t 
     "    2   operational error\n"
     "    3   when --is argument doesn't match\n"
     ),
-   {'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is} },
+   {'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is,
+    svnauthz__groups_file} },
   { NULL, NULL, {0}, NULL, {0} }
 };
 
@@ -178,20 +184,40 @@ subcommand_help(apr_getopt_t *os, void *
   return SVN_NO_ERROR;
 }
 
+/* Loads the fs FILENAME contents into *CONTENTS ensuring that the
+   corresponding node is a file. Using POOL for allocations. */
+static svn_error_t *
+read_file_contents(svn_stream_t **contents, const char *filename,
+                   svn_fs_root_t *root, apr_pool_t *pool)
+{
+  svn_node_kind_t node_kind;
+
+  /* Make sure the path is a file */
+  SVN_ERR(svn_fs_check_path(&node_kind, root, filename, pool));
+  if (node_kind != svn_node_file)
+    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
+                             "Path '%s' is not a file", filename);
+
+  SVN_ERR(svn_fs_file_contents(contents, root, filename, pool));
+
+  return SVN_NO_ERROR;
+}
+
 /* Loads the authz config into *AUTHZ from the file at AUTHZ_FILE
-   in repository at REPOS_PATH from the transaction TXN_NAME.  Using
-   POOL for allocations. */
+   in repository at REPOS_PATH from the transaction TXN_NAME.  If GROUPS_FILE
+   is set, the resulting *AUTHZ will be constructed from AUTHZ_FILE with
+   global groups taken from GROUPS_FILE.  Using POOL for allocations. */
 static svn_error_t *
 get_authz_from_txn(svn_authz_t **authz, const char *repos_path,
-                   const char *authz_file, const char *txn_name,
-                   apr_pool_t *pool)
+                   const char *authz_file, const char *groups_file,
+                   const char *txn_name, apr_pool_t *pool)
 {
   svn_repos_t *repos;
   svn_fs_t *fs;
   svn_fs_txn_t *txn;
   svn_fs_root_t *root;
-  svn_node_kind_t node_kind;
-  svn_stream_t *contents;
+  svn_stream_t *authz_contents;
+  svn_stream_t *groups_contents;
   svn_error_t *err;
 
   /* Open up the repository and find the transaction root */
@@ -200,14 +226,16 @@ get_authz_from_txn(svn_authz_t **authz, 
   SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, pool));
   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
 
-  /* Make sure the path is a file */
-  SVN_ERR(svn_fs_check_path(&node_kind, root, authz_file, pool));
-  if (node_kind != svn_node_file)
-    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
-                             "Path '%s' is not a file", authz_file);
+  /* Get the authz file contents. */
+  SVN_ERR(read_file_contents(&authz_contents, authz_file, root, pool));
 
-  SVN_ERR(svn_fs_file_contents(&contents, root, authz_file, pool));
-  err = svn_repos_authz_parse(authz, contents, pool);
+  /* Get the groups file contents if needed. */
+  if (groups_file)
+    SVN_ERR(read_file_contents(&groups_contents, groups_file, root, pool));
+  else
+    groups_contents = NULL;
+
+  err = svn_repos_authz_parse(authz, authz_contents, groups_contents, pool);
 
   /* Add the filename to the error stack since the parser doesn't have it. */
   if (err != SVN_NO_ERROR)
@@ -218,8 +246,10 @@ get_authz_from_txn(svn_authz_t **authz, 
 }
 
 /* Loads the authz config into *AUTHZ from OPT_STATE->AUTHZ_FILE.  If
-   OPT_STATE->TXN is set then OPT_STATE->AUTHZ_FILE is treated as a fspath
-   in repository at OPT_STATE->REPOS_PATH. */
+   OPT_STATE->GROUPS_FILE is set, loads the global groups from it.
+   If OPT_STATE->TXN is set then OPT_STATE->AUTHZ_FILE and
+   OPT_STATE->GROUPS_FILE are treated as fspaths in repository at
+   OPT_STATE->REPOS_PATH. */
 static svn_error_t *
 get_authz(svn_authz_t **authz, struct svnauthz_opt_state *opt_state,
           apr_pool_t *pool)
@@ -227,10 +257,14 @@ get_authz(svn_authz_t **authz, struct sv
   /* Read the access file and validate it. */
   if (opt_state->txn)
     return get_authz_from_txn(authz, opt_state->repos_path,
-                              opt_state->authz_file, opt_state->txn, pool);
+                              opt_state->authz_file,
+                              opt_state->groups_file,
+                              opt_state->txn, pool);
 
   /* Else */
-  return svn_repos_authz_read2(authz, opt_state->authz_file, TRUE, NULL, pool);
+  return svn_repos_authz_read2(authz, opt_state->authz_file,
+                               opt_state->groups_file,
+                               TRUE, NULL, pool);
 }
 
 static svn_error_t *
@@ -374,6 +408,55 @@ use_compat_mode(const char *cmd, apr_poo
                       sizeof(SVNAUTHZ_COMPAT_NAME)-1);
 }
 
+/* Canonicalize ACCESS_FILE into *CANONICALIZED_ACCESS_FILE based on the type
+   of argument.  Error out on unsupported path types.  If WITHIN_TXN is set,
+   ACCESS_FILE has to be a fspath in the repo.  Use POOL for allocations. */
+static svn_error_t *
+canonicalize_access_file(const char **canonicalized_access_file,
+                         const char *access_file,
+                         svn_boolean_t within_txn,
+                         apr_pool_t *pool)
+{
+  if (svn_path_is_repos_relative_url(access_file))
+    {
+      /* Can't accept repos relative urls since we don't have the path to
+       * the repository. */
+      return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                               ("'%s' is a repository relative URL when it "
+                               "should be a local path or file:// URL"),
+                               access_file);
+    }
+  else if (svn_path_is_url(access_file))
+    {
+      if (within_txn)
+        {
+          /* Don't allow urls with transaction argument. */
+          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+                                   ("'%s' is a URL when it should be a "
+                                   "repository-relative path"),
+                                   access_file);
+        }
+
+      *canonicalized_access_file = svn_uri_canonicalize(access_file, pool);
+    }
+  else if (within_txn)
+    {
+      /* Transaction flag means this has to be a fspath to the access file
+       * in the repo. */
+      *canonicalized_access_file =
+          svn_fspath__canonicalize(access_file, pool);
+    }
+  else
+    {
+      /* If it isn't a URL and there's no transaction flag then it's a
+       * dirent to the access file on local disk. */
+      *canonicalized_access_file =
+          svn_dirent_internal_style(access_file, pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
 static int
 sub_main(int argc, const char *argv[], apr_pool_t *pool)
 {
@@ -392,7 +475,7 @@ sub_main(int argc, const char *argv[], a
 
   /* Initialize opt_state */
   opt_state.username = opt_state.fspath = opt_state.repos_name = NULL;
-  opt_state.txn = opt_state.repos_path = NULL;
+  opt_state.txn = opt_state.repos_path = opt_state.groups_file = NULL;
 
   /* Parse options. */
   SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
@@ -443,6 +526,11 @@ sub_main(int argc, const char *argv[], a
             case svnauthz__is:
               SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.is, arg, pool));
               break;
+            case svnauthz__groups_file:
+              SVN_INT_ERR(
+                  svn_utf_cstring_to_utf8(&opt_state.groups_file,
+                                          arg, pool));
+              break;
             default:
                 {
                   SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
@@ -543,47 +631,18 @@ sub_main(int argc, const char *argv[], a
       SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.authz_file, os->argv[os->ind],
                                           pool));
 
-      /* Canonicalize opt_state.authz_file appropriately */
-      if (svn_path_is_repos_relative_url(opt_state.authz_file))
-        {
-          /* Can't accept repos relative urls since we don't have the path to
-           * the repository. */
-          err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
-                                  ("'%s' is a repository relative URL when it "
-                                   "should be a local path or file:// URL"),
-                                  opt_state.authz_file);
-          return EXIT_ERROR(err, EXIT_FAILURE);
-        }
-      else if (svn_path_is_url(opt_state.authz_file))
-        {
-          if (opt_state.txn) 
-            {
-              /* don't allow urls with transaction argument */
-              err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
-                                      ("'%s' is a URL when it should be a "
-                                       "repository-relative path"),
-                                      opt_state.authz_file);
-              return EXIT_ERROR(err, EXIT_FAILURE);
-            }
-
-          opt_state.authz_file = svn_uri_canonicalize(opt_state.authz_file,
-                                                      pool);
+      /* Canonicalize opt_state.authz_file appropriately. */
+      SVN_INT_ERR(canonicalize_access_file(&opt_state.authz_file,
+                                           opt_state.authz_file,
+                                           opt_state.txn != NULL, pool));
+
+      /* Same for opt_state.groups_file if it is present. */
+      if (opt_state.groups_file)
+        {
+          SVN_INT_ERR(canonicalize_access_file(&opt_state.groups_file,
+                                               opt_state.groups_file,
+                                               opt_state.txn != NULL, pool));
         }
-      else if (opt_state.txn)
-        {
-          /* Transaction flag means this has to be a fspath to the authz_file
-           * in the repo. */
-          opt_state.authz_file =
-              svn_fspath__canonicalize(opt_state.authz_file, pool);
-        }
-      else
-        {
-          /* If it isn't a URL and there's no transaction flag then it's a
-           * dirent to a authz_file on local disk.  */
-          opt_state.authz_file = svn_dirent_internal_style(opt_state.authz_file,
-                                                           pool);
-        }
-
     }
 
   /* Check that the subcommand wasn't passed any inappropriate options. */