You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2014/08/04 05:14:15 UTC

svn commit: r1615480 - in /subversion/branches/authzperf: ./ subversion/libsvn_repos/ subversion/tests/libsvn_repos/

Author: brane
Date: Mon Aug  4 03:14:15 2014
New Revision: 1615480

URL: http://svn.apache.org/r1615480
Log:
On the authzperf branch: Invent an in-memory representation for authz
info and write a config parser constructor that populates it.

* BRANCH-README: Update TODO and DONE lists.
* build.conf (authz-test): Declare new C test.

* subversion/libsvn_repos/authz.h,
  subversion/libsvn_repos/authz_parse.c:
   Parser declarations and implementation.

* subversion/tests/libsvn_repos/authz-test.c,
  subversion/tests/libsvn_repos/authz.rules,
  subversion/tests/libsvn_repos/authz.groups:
   New; test(s) for the authz file parser.

Added:
    subversion/branches/authzperf/subversion/libsvn_repos/authz.h   (with props)
    subversion/branches/authzperf/subversion/libsvn_repos/authz_parse.c   (with props)
    subversion/branches/authzperf/subversion/tests/libsvn_repos/authz-test.c
    subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.groups   (with props)
    subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.rules   (with props)
Modified:
    subversion/branches/authzperf/BRANCH-README
    subversion/branches/authzperf/build.conf

Modified: subversion/branches/authzperf/BRANCH-README
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/BRANCH-README?rev=1615480&r1=1615479&r2=1615480&view=diff
==============================================================================
--- subversion/branches/authzperf/BRANCH-README (original)
+++ subversion/branches/authzperf/BRANCH-README Mon Aug  4 03:14:15 2014
@@ -7,9 +7,13 @@ https://wiki.apache.org/subversion/Authz
 
 TODO:
 
-* implement an authz-specific constructor for the config parser
 * implement precedence rules
-* implement value-expansion mode in the config parser
+* calculate global access rules in svn_authz_tng_t
+* rewrite the filtered rules generator to use svn_authz_tng_t
+* rename svn_authz_tng_t to svn_authz_t
+* implement wildcard escaping in glob rules
+* expose the svn_string_t buffers in the config parser constructor
+  and use the known sizes when calling intern_string in authz_parse.c
 
 DONE:
 
@@ -24,3 +28,4 @@ DONE:
 * add support for prefix segment patterns ("/foo*/")
 * add support for suffix segment patterns ("/*foo/")
 * add support for arbitrary patterns ("/foo*bar*baz/" etc.)
+* implement an authz-specific constructor for the config parser

Modified: subversion/branches/authzperf/build.conf
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/build.conf?rev=1615480&r1=1615479&r2=1615480&view=diff
==============================================================================
--- subversion/branches/authzperf/build.conf (original)
+++ subversion/branches/authzperf/build.conf Mon Aug  4 03:14:15 2014
@@ -853,6 +853,14 @@ libs = libsvn_test libsvn_fs libsvn_delt
 # ----------------------------------------------------------------------------
 # Tests for libsvn_repos
 
+[authz-test]
+description = Test authz parsing in libsvn_repos
+type = exe
+path = subversion/tests/libsvn_repos
+sources = authz-test.c
+install = test
+libs = libsvn_test libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr
+
 [repos-test]
 description = Test delta editor in libsvn_repos
 type = exe

Added: subversion/branches/authzperf/subversion/libsvn_repos/authz.h
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/authz.h?rev=1615480&view=auto
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/authz.h (added)
+++ subversion/branches/authzperf/subversion/libsvn_repos/authz.h Mon Aug  4 03:14:15 2014
@@ -0,0 +1,182 @@
+/* authz.h : authz parsing and searching, private to libsvn_repos
+ *
+ * ====================================================================
+ *    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.
+ * ====================================================================
+ */
+
+#ifndef SVN_REPOS_AUTHZ_H
+#define SVN_REPOS_AUTHZ_H
+
+#include <apr_hash.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_repos.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*
+ *   Authz and global group file parsing
+ */
+
+/* Number of (user, respoitory) combinations per authz for which we can
+ * cache the corresponding filtered path rule trees.
+ *
+ * Since authz instance are per connection and there is usually only one
+ * repository per connection, 2 (user + anonymous) would be sufficient in
+ * most cases.  Having 4 adds plenty of headroom and we expect high locality
+ * in any case.
+ *
+ * ### This number will be far too low if/when the parsed authz info
+ *     becomes shared between multiple sessions.
+ */
+#define AUTHZ_FILTERED_CACHE_SIZE 4
+
+
+/* A dictionary of rules that are specific to a particular
+   (user, repository) combination. */
+typedef struct authz_user_rules_t authz_user_rules_t;
+
+
+/* Accumulated global rights for (user, repository). */
+typedef struct authz_global_rights_t
+{
+  /* Interned key of this instance in svn_authz_tng_t::global_rights. */
+  const char *key;
+
+  /* The lowest level of access that the user has to every
+     path in the repository. */
+  svn_repos_authz_access_t min_access;
+
+  /* The highest level of access that the user has to
+     any path in the repository. */
+  svn_repos_authz_access_t max_access;
+} authz_global_rights_t;
+
+
+/* Immutable authorization info */
+typedef struct svn_authz_tng_t
+{
+  /* All ACLs from the authz file, in the order of definition. */
+  apr_array_header_t *acls;
+
+  /* Globally accumulated rights, for all concrete users mentioned
+     in the authz file, indexed by (user, repository). */
+  apr_hash_t *global_rights;
+
+  /* Fully recursively expanded group definitions, indexed by group name. */
+  apr_hash_t *groups;
+
+  /* A cache of rules filtered for a particular user.
+     These will be generated on-demand. */
+  authz_user_rules_t *user_rules[AUTHZ_FILTERED_CACHE_SIZE];
+
+  /* The pool from which all the parsed authz data is allocated.
+     This is hte RESULT_POOL passed to svn_authz__tng_parse.
+
+     It's a good idea to dedicate a pool for the authz structure, so
+     that the whole authz representation can be deallocated by
+     destroying the pool. */
+  apr_pool_t *pool;
+} svn_authz_tng_t;
+
+
+/* An access control list defined by access rules. */
+typedef struct authz_acl_t
+{
+  /* The sequence number of the ACL stores the order in which access
+     rules were defined in the authz file. The authz lookup code
+     selects the highest-numbered ACL from amongst a set of equivalent
+     matches. */
+  apr_int64_t sequence_number;
+
+  /* The reposttory name from the rule. This will be empty string if a
+     the rule did not name a repository. */
+  const char *repos;
+
+  /* The path (or pattern) part of the rule, including the leading /. */
+  const char *rule;
+
+  /* TRUE if RULE contains wildcards. */
+  svn_boolean_t glob;
+
+  /* Access rigts for anonymous users */
+  svn_boolean_t has_anon_access;
+  svn_repos_authz_access_t anon_access;
+
+  /* Access rigts for authenticated users */
+  svn_boolean_t has_authn_access;
+  svn_repos_authz_access_t authn_access;
+
+  /* All other user- or group-specific access rights.
+     Aliases are replaced with their definitions, rules for the same
+     user or group are merged. */
+  apr_array_header_t *user_access;
+} authz_acl_t;
+
+
+/* An access control entry in authz_acl_t::user_access. */
+typedef struct authz_ace_t
+{
+  /* The name of the alias, user or group that this ACE applies to. */
+  const char *name;
+
+  /* True if this is an inverse-match rule. */
+  svn_boolean_t inverted;
+
+  /* The access rights defined by this ACE. */
+  svn_repos_authz_access_t access;
+} authz_ace_t;
+
+
+/* Parse authz definitions from RULES and optional global group
+ * definitions from GROUPS, returning an immutable, in-memory
+ * representation of all the rules, groups and aliases.
+ *
+ * **AUTHZ and its contents will be allocated from RESULT_POOL.
+ * The function uses SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_authz__tng_parse(svn_authz_tng_t **authz,
+                     svn_stream_t *rules,
+                     svn_stream_t *groups,
+                     apr_pool_t *result_pool,
+                     apr_pool_t *scratch_pool);
+
+
+/*
+ *   Authorization lookup
+ */
+
+/* The "anonymous" user for authz queries. */
+#define AUTHZ_ANONYMOUS_USER ((const char*)0)
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_REPOS_AUTHZ_H */

Propchange: subversion/branches/authzperf/subversion/libsvn_repos/authz.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: subversion/branches/authzperf/subversion/libsvn_repos/authz_parse.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/authz_parse.c?rev=1615480&view=auto
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/authz_parse.c (added)
+++ subversion/branches/authzperf/subversion/libsvn_repos/authz_parse.c Mon Aug  4 03:14:15 2014
@@ -0,0 +1,810 @@
+/* authz_parse.c : Parser for path-based access control
+ *
+ * ====================================================================
+ *    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.
+ * ====================================================================
+ */
+
+#include <apr_tables.h>
+
+#include "svn_ctype.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_iter.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_config_private.h"
+
+#include "svn_private_config.h"
+
+#include "authz.h"
+
+
+/* Temporary ACL constructed by the parser. */
+typedef struct parsed_acl_t
+{
+  /* The global ACL.
+     ACL.repos and ACL>rule are allocated from the result pool.
+     ACL.user_access is null during the parsing stage. */
+  authz_acl_t acl;
+
+  /* The set of access control entries. In the second pass, aliases in
+     these entries will be expanded and equivalent entries will be
+     merged. The entries are allocated from the parser pool. */
+  apr_hash_t *aces;
+
+  /* The set of access control entries that use aliases. In the second
+     pass, aliases in these entries will be expanded and merged into ACES.
+     The entries are allocated from the parser pool. */
+  apr_hash_t *alias_aces;
+} parsed_acl_t;
+
+
+/* Temporary group definition constructed by the authz/group parser.
+   Once all groups and aliases are defined, a second pass over these
+   data will recursively expand group memberships. */
+typedef struct parsed_group_t
+{
+  svn_boolean_t local_group;
+  apr_array_header_t *members;
+} parsed_group_t;
+
+
+/* Baton for the parser constructor. */
+typedef struct ctor_baton_t
+{
+  /* The final output of the parser. */
+  svn_authz_tng_t *authz;
+
+  /* Interned-string set, allocated in AUTHZ->pool.
+     Stores singleton instances of user, group and repository names,
+     which are used by members of the AUTHZ structure. By reusing the
+     same immutable string multiple times, we reduce the size of the
+     authz representation in the result pool.
+
+     N.B.: Whilst the strings are allocated from teh result pool, the
+     hash table itself is not. */
+  apr_hash_t *strings;
+
+  /* A set of all the sections that were seen in the authz or global
+     groups file. Rules, aliases and groups may each only be defined
+     once in the authz file. The global groups file may only contain a
+     [groups] section. */
+  apr_hash_t *sections;
+
+  /* The name of the section we're currently parsing. */
+  const char *section;
+
+  /* TRUE iff we're parsing the global groups file. */
+  svn_boolean_t parsing_groups;
+
+  /* TRUE iff we're parsing a [groups] section. */
+  svn_boolean_t in_groups;
+
+  /* TRUE iff we're parsing an [aliases] section. */
+  svn_boolean_t in_aliases;
+
+  /* Temporary parsed-groups definitions. */
+  apr_hash_t *parsed_groups;
+
+  /* Temporary alias mappings. */
+  apr_hash_t *parsed_aliases;
+
+  /* temporary parsed-acl definitions. */
+  apr_array_header_t *parsed_acls;
+
+  /* The temporary ACL we're currently constructing. */
+  parsed_acl_t *current_acl;
+
+  /* The parser's scratch pool. This may not be the same pool as
+     passed to the constructor callbacks, that is supposed to be an
+     iteration pool maintained by the generic parser.
+
+     N.B.: The result pool is AUTHZ->pool. */
+  apr_pool_t *parser_pool;
+} ctor_baton_t;
+
+
+/* The name of the aliases section. */
+static const char aliases_section[] = "aliases";
+
+/* The name of the groups section. */
+static const char groups_section[] = "groups";
+
+/* The token indicating that an authz rule contains wildcards. */
+static const char glob_rule_token[] = "glob";
+
+/* The anonymous access token. */
+static const char anon_access_token[] = "$anonymous";
+
+/* The authenticated access token. */
+static const char authn_access_token[] = "$authenticated";
+
+
+/* Initialize a constuctor baton. */
+static ctor_baton_t *
+create_ctor_baton(apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  apr_pool_t *const parser_pool = svn_pool_create(scratch_pool);
+  ctor_baton_t *const cb = apr_pcalloc(parser_pool, sizeof(*cb));
+
+  svn_authz_tng_t *const authz = apr_pcalloc(result_pool, sizeof(*authz));
+  authz->pool = result_pool;
+
+  cb->authz = authz;
+  cb->strings = svn_hash__make(parser_pool);
+
+  cb->sections = svn_hash__make(parser_pool);
+  cb->section = NULL;
+  cb->parsing_groups = FALSE;
+  cb->in_groups = FALSE;
+  cb->in_aliases = FALSE;
+
+  cb->parsed_groups = svn_hash__make(parser_pool);
+  cb->parsed_aliases = svn_hash__make(parser_pool);
+  cb->parsed_acls = apr_array_make(parser_pool, 64, sizeof(parsed_acl_t));
+  cb->current_acl = NULL;
+
+  cb->parser_pool = parser_pool;
+
+  return cb;
+}
+
+
+/* Internalize a string that will be referenced by the parsed svn_authz_t.
+   If LEN is (apr_size_t)-1, assume the string is NUL-terminated. */
+static const char *
+intern_string(ctor_baton_t *cb, const char *str, apr_size_t len)
+{
+  const char *interned;
+
+  if (len == (apr_size_t)-1)
+    len = strlen(str);
+
+  interned = apr_hash_get(cb->strings, str, len);
+  if (!interned)
+    {
+      interned = apr_pstrmemdup(cb->authz->pool, str, len);
+      apr_hash_set(cb->strings, interned, len, interned);
+    }
+  return interned;
+}
+
+
+/* Helper for rules_open_section and groups_open_section. */
+static svn_error_t *
+check_open_section(ctor_baton_t *cb, const char *section)
+{
+  SVN_ERR_ASSERT(!cb->current_acl && !cb->section);
+  if (svn_hash_gets(cb->sections, section))
+    {
+      if (cb->parsing_groups)
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Section appears more than once"
+              " in the global groups file: [%s]"),
+            section);
+      else
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Section appears more than once"
+              " in the authz file: [%s]"),
+            section);
+    }
+
+  cb->section = apr_pstrdup(cb->parser_pool, section);
+  svn_hash_sets(cb->sections,  cb->section, "");
+  return SVN_NO_ERROR;
+}
+
+
+/* Constructor callback: Begins the [groups] section. */
+static svn_error_t *
+groups_open_section(void *baton, const char *section)
+{
+  ctor_baton_t *const cb = baton;
+
+  if (cb->parsing_groups)
+    SVN_ERR(check_open_section(cb, section));
+
+  if (0 == strcmp(section, groups_section))
+    {
+      cb->in_groups = TRUE;
+      return SVN_NO_ERROR;
+    }
+
+  return svn_error_createf(
+      SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+      (cb->parsing_groups
+       ? _("Section is not valid in the global group file: [%s]")
+       : _("Section is not valid in the authz file: [%s]")),
+      section);
+}
+
+
+/* Constructor callback: Parses a group declaration. */
+static svn_error_t *
+groups_add_value(void *baton, const char *section,
+                 const char *option, const char *value)
+{
+  ctor_baton_t *const cb = baton;
+  const char *group;
+
+  SVN_ERR_ASSERT(cb->in_groups);
+
+  if (strchr("@$&*~", *option))
+    {
+      if (cb->parsing_groups)
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Global group name '%s' may not begin with '%c'"),
+            option, *option);
+      else
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Group name '%s' may not begin with '%c'"),
+            option, *option);
+    }
+
+  /* Decorate the name to make lookups consistent. */
+  group = apr_pstrcat(cb->parser_pool, "@", option, SVN_VA_NULL);
+  if (svn_hash_gets(cb->parsed_groups, group))
+    {
+      if (cb->parsing_groups)
+        return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                                 _("Can't override definition"
+                                   " of global group '%s'"),
+                                 group);
+      else
+        return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                                 _("Can't override definition"
+                                   " of group '%s'"),
+                                 group);
+    }
+
+  /* We store the whole group definition, so that we can use the
+     temporary groups in the baton hash later to fully expand group
+     memberships.
+     At this point, we can finally internalize the group name. */
+  svn_hash_sets(cb->parsed_groups, intern_string(cb, group, -1),
+                svn_cstring_split(value, ",", TRUE, cb->parser_pool));
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Constructor callback: Starts a rule or [aliases] section. */
+static svn_error_t *
+rules_open_section(void *baton, const char *section)
+{
+  ctor_baton_t *const cb = baton;
+  const char *rule = section;
+  const char *endp;
+  parsed_acl_t acl;
+
+  SVN_ERR(check_open_section(cb, section));
+
+  if (*rule != ':')
+    acl.acl.glob = FALSE;
+  else
+    {
+      /* This must be a wildcard rule. */
+      endp = strchr(++rule, ':');
+      if (!endp)
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Empty repository name in authz rule [%s]"),
+            section);
+      if (strncmp(rule, glob_rule_token, endp - rule))
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Invalid type token '%s' in authz rule [%s]"),
+            apr_pstrmemdup(cb->parser_pool, rule, endp - rule),
+            section);
+
+      acl.acl.glob = TRUE;
+      rule = endp + 1;
+    }
+
+  /* FIXME: Colons are potentially valid in paths; should check for a
+     path first to make the parser more robust. */
+  endp = strchr(rule, ':');
+  if (!endp)
+    acl.acl.repos = "";
+  else
+    {
+      /* The rule contains a repository name. */
+      if (endp == rule)
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Empty repository name in authz rule [%s]"),
+            section);
+      if (memchr(rule, '/', endp - rule))
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Invalid repository name '%s' in authz rule [%s]"),
+            apr_pstrmemdup(cb->parser_pool, rule, endp - rule),
+            section);
+
+      acl.acl.repos = intern_string(cb, rule, endp - rule);
+      rule = endp + 1;
+    }
+
+  if (*rule == '/')
+    {
+      ++rule;
+      if (svn_fspath__is_canonical(rule))
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Non-canonical path '%s' in in authz rule [%s]"),
+            rule, section);
+
+      acl.acl.rule = intern_string(cb, rule, -1);
+    }
+  else if (0 == strcmp(section, aliases_section))
+    {
+      cb->in_aliases = TRUE;
+      return SVN_NO_ERROR;
+    }
+  else
+    {
+      /* This must be the [groups] section. */
+      return groups_open_section(cb, section);
+    }
+
+  acl.acl.sequence_number = cb->parsed_acls->nelts;
+  acl.acl.anon_access = svn_authz_none;
+  acl.acl.has_anon_access = FALSE;
+  acl.acl.authn_access = svn_authz_none;
+  acl.acl.has_authn_access = FALSE;
+  acl.acl.user_access = NULL;
+
+  acl.aces = svn_hash__make(cb->parser_pool);
+  acl.alias_aces = svn_hash__make(cb->parser_pool);
+
+  cb->current_acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t);
+  *cb->current_acl = acl;
+  return SVN_NO_ERROR;
+}
+
+
+/* Constructor callback: Parse a rule, alias or group delcaration. */
+static svn_error_t *
+rules_add_value(void *baton, const char *section,
+                const char *option, const char *value)
+{
+  ctor_baton_t *const cb = baton;
+
+  if (cb->in_groups)
+    return groups_add_value(baton, section, option, value);
+
+  if (cb->in_aliases)
+    {
+      const char *alias;
+
+      if (strchr("@$&*~", *option))
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Alias name '%s' may not begin with '%c'"),
+            option, *option);
+
+      /* Decorate the name to make lookups consistent. */
+      alias = apr_pstrcat(cb->parser_pool, "&", option, SVN_VA_NULL);
+
+      if (svn_hash_gets(cb->parsed_aliases, alias))
+        return svn_error_createf(
+            SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+            _("Can't override definition of alias '%s'"),
+            alias);
+
+      svn_hash_sets(cb->parsed_aliases, alias,
+                    intern_string(cb, value, -1));
+    }
+  else
+    {
+      parsed_acl_t *const acl = cb->current_acl;
+      const char *name = option;
+      const svn_boolean_t inverted = (*name == '~');
+      svn_boolean_t anonymous = FALSE;
+      svn_boolean_t authenticated = FALSE;
+      svn_repos_authz_access_t access = svn_authz_none;
+      authz_ace_t *ace;
+      int i;
+
+      SVN_ERR_ASSERT(acl != NULL);
+
+      if (inverted)
+        ++name;
+
+      /* Determine the access entry type. */
+      switch (*name)
+        {
+        case '~':
+          return svn_error_createf(
+              SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+              _("Access entry '%s' has more than one inversion;"
+                " double negatives are not permitted"),
+              option);
+          break;
+
+        case '*':
+          if (name[1] != '\0')
+            return svn_error_createf(
+                SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                _("Access entry '%s' is not valid;"
+                  " it must be a single '*'"),
+                option);
+
+          if (inverted)
+            return svn_error_createf(
+                SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                _("Access entry '~*' will never match"));
+
+          anonymous = TRUE;
+          authenticated = TRUE;
+          break;
+
+        case '$':
+          if (0 == strcmp(name, anon_access_token))
+            {
+              if (inverted)
+                authenticated = TRUE;
+              else
+                anonymous = TRUE;
+            }
+          else if (0 == strcmp(name, authn_access_token))
+            {
+              if (inverted)
+                anonymous = TRUE;
+              else
+                authenticated = TRUE;
+            }
+          else
+            return svn_error_createf(
+                SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                _("Access entry token '%s' is not valid;"
+                  " should be '%s' or '%s'"),
+                option, anon_access_token, authn_access_token);
+          break;
+
+        default:
+          /* A username, group name or alias. */;
+        }
+
+      /* Parse the access rights. */
+      for (i = 0; value[i]; ++i)
+        switch (value[i])
+          {
+          case 'r':
+            access |= svn_authz_read;
+            break;
+
+          case 'w':
+            /* FIXME: Idiocy. Write access should imply read access. */
+            access |= svn_authz_write;
+            break;
+
+          default:
+            if (!svn_ctype_isspace(value[i]))
+              return svn_error_createf(
+                  SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                  _("The access mode '%c' in access entry '%s'"
+                    "of rule [%s] is not valid"),
+                  value[i], option, section);
+          }
+
+      /* Update the parsed ACL with this access entry. */
+      if (anonymous || authenticated)
+        {
+          if (anonymous)
+            {
+              acl->acl.has_anon_access = TRUE;
+              acl->acl.anon_access |= access;
+            }
+          if (authenticated)
+            {
+              acl->acl.has_authn_access = TRUE;
+              acl->acl.authn_access |= access;
+            }
+        }
+      else
+        {
+          /* The inversion tag must be part of the key in the hash
+             table, otherwise we can't tell regular and inverted
+             entries appart. */
+          const char *key = (inverted ? name - 1 : name);
+          const svn_boolean_t aliased = (*name == '&');
+          apr_hash_t *aces = (aliased ? acl->alias_aces : acl->aces);
+
+          ace = svn_hash_gets(aces, key);
+          if (ace)
+            ace->access |= access;
+          else
+            {
+              ace = apr_palloc(cb->parser_pool, sizeof(*ace));
+              ace->name = (aliased
+                           ? apr_pstrdup(cb->parser_pool, name)
+                           : intern_string(cb, name, -1));
+              ace->inverted = inverted;
+              ace->access = access;
+
+              key = (inverted
+                     ? apr_pstrdup(cb->parser_pool, key)
+                     : ace->name);
+              svn_hash_sets(aces, key, ace);
+            }
+        }
+    }
+  return SVN_NO_ERROR;
+}
+
+
+/* Constructor callback: Close a section. */
+static svn_error_t *
+close_section(void *baton, const char *section)
+{
+  ctor_baton_t *const cb = baton;
+
+  SVN_ERR_ASSERT(0 == strcmp(cb->section, section));
+  cb->section = NULL;
+  cb->current_acl = NULL;
+  cb->in_groups = FALSE;
+  cb->in_aliases = FALSE;
+  return SVN_NO_ERROR;
+}
+
+
+/* Add a user to GROUP.
+   GROUP is never internalized, but USER always is. */
+static void
+add_to_group(ctor_baton_t *cb, const char *group, const char *user)
+{
+  apr_hash_t *members = svn_hash_gets(cb->authz->groups, group);
+  if (!members)
+    {
+      group = intern_string(cb, group, -1);
+      members = svn_hash__make(cb->authz->pool);
+      svn_hash_sets(cb->authz->groups, group, members);
+    }
+  svn_hash_sets(members, user, "");
+}
+
+
+/* Hash iterator for expanding group definitions.
+   WARNING: This function is recursive! */
+static svn_error_t *
+expand_group_callback(void *baton,
+                      const void *key,
+                      apr_ssize_t klen,
+                      void *value,
+                      apr_pool_t *scratch_pool)
+{
+  ctor_baton_t *const cb = baton;
+  const char *const group = key;
+  apr_array_header_t *members = value;
+
+  int i;
+  for (i = 0; i < members->nelts; ++i)
+    {
+      const char *member = APR_ARRAY_IDX(members, i, const char*);
+      if (0 == strcmp(member, group))
+            return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                                     _("Recursive definition of group '%s'"),
+                                     group);
+
+      if (*member == '&')
+        {
+          /* Add expanded alias to the group.
+             N.B.: the user name is already internalized. */
+          const char *user = svn_hash_gets(cb->parsed_aliases, member);
+          if (!user)
+            return svn_error_createf(
+                SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                _("Alias '%s' was never defined"),
+                member);
+
+          add_to_group(cb, group, user);
+        }
+      else if (*member != '@')
+        {
+          /* Add the member to the group. */
+          add_to_group(cb, group, intern_string(cb, member, -1));
+        }
+      else
+        {
+          /* Recursively expand the group membership */
+          members = svn_hash_gets(cb->parsed_groups, member);
+          if (!members)
+            return svn_error_createf(
+                SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                _("Undefined group '%s'"),
+                member);
+          SVN_ERR(expand_group_callback(cb, key, klen,
+                                        members, scratch_pool));
+        }
+    }
+  return SVN_NO_ERROR;
+}
+
+
+/* Hash iteration baton for merge_alias_ace. */
+typedef struct merge_alias_baton_t
+{
+  apr_hash_t *aces;
+  ctor_baton_t *cb;
+} merge_alias_baton_t;
+
+/* Hash iterator for expanding and mergina alias-based ACEs
+   into the user/group-based ACEs. */
+static svn_error_t *
+merge_alias_ace(void *baton,
+                const void *key,
+                apr_ssize_t klen,
+                void *value,
+                apr_pool_t *scratch_pool)
+{
+  merge_alias_baton_t *const mab = baton;
+  authz_ace_t *aliased_ace = value;
+  const char *alias = aliased_ace->name;
+  const char *unaliased_key;
+  const char *user;
+  authz_ace_t *ace;
+
+  user = svn_hash_gets(mab->cb->parsed_aliases, alias);
+  if (!user)
+    return svn_error_createf(
+        SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+        _("Alias '%s' was never defined"),
+        alias);
+
+  /* N.B.: The user name is always internalized,
+     but the inverted key may not be. */
+  if (!aliased_ace->inverted)
+    unaliased_key = user;
+  else
+    {
+      unaliased_key = apr_pstrcat(mab->cb->parser_pool,
+                                  "~", user, SVN_VA_NULL);
+      unaliased_key = intern_string(mab->cb, unaliased_key, -1);
+    }
+
+  ace = svn_hash_gets(mab->aces, unaliased_key);
+  if (!ace)
+    {
+      aliased_ace->name = user;
+      svn_hash_sets(mab->aces, unaliased_key, aliased_ace);
+    }
+  else
+    {
+      SVN_ERR_ASSERT(!ace->inverted == !aliased_ace->inverted);
+      ace->access |= aliased_ace->access;
+    }
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Hash iterator, inserts an ACE into the ACLs array. */
+static svn_error_t *
+array_insert_ace(void *baton,
+                 const void *key,
+                 apr_ssize_t klen,
+                 void *value,
+                 apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *ace_array = baton;
+  authz_ace_t *ace = value;
+  APR_ARRAY_PUSH(ace_array, authz_ace_t) = *ace;
+  return SVN_NO_ERROR;
+}
+
+
+/* List iterator, expands/merges a parsed ACL into its final form and
+   appends it to the authz info's ACL array. */
+static svn_error_t *
+expand_acl_callback(void *baton,
+                    void *item,
+                    apr_pool_t *scratch_pool)
+{
+  ctor_baton_t *const cb = baton;
+  parsed_acl_t *const pacl = item;
+
+  /* Expand and merge the aliased ACEs. */
+  if (apr_hash_count(pacl->alias_aces))
+    {
+      merge_alias_baton_t mab;
+      mab.aces = pacl->aces;
+      mab.cb = cb;
+      SVN_ERR(svn_iter_apr_hash(NULL, pacl->alias_aces,
+                                merge_alias_ace, &mab, scratch_pool));
+    }
+
+  /* Make an array from the merged hashes. */
+  pacl->acl.user_access =
+    apr_array_make(cb->authz->pool, apr_hash_count(pacl->aces),
+                   sizeof(authz_ace_t));
+  SVN_ERR(svn_iter_apr_hash(NULL, pacl->aces,
+                            array_insert_ace, pacl->acl.user_access,
+                            scratch_pool));
+
+  /* And finally store the completed ACL into authz. */
+  APR_ARRAY_PUSH(cb->authz->acls, authz_acl_t) = pacl->acl;
+
+  /* TODO: Calculate global access rules. */
+
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_authz__tng_parse(svn_authz_tng_t **authz,
+                     svn_stream_t *rules,
+                     svn_stream_t *groups,
+                     apr_pool_t *result_pool,
+                     apr_pool_t *scratch_pool)
+{
+  ctor_baton_t *const cb = create_ctor_baton(result_pool, scratch_pool);
+
+  /* Pass 1: Parse the authz file. */
+  SVN_ERR(svn_config__parse_stream(rules,
+                                   svn_config__constructor_create(
+                                       rules_open_section,
+                                       close_section,
+                                       rules_add_value,
+                                       cb->parser_pool),
+                                   cb, cb->parser_pool));
+
+  /* Pass 1.6487: Parse the global groups file. */
+  if (groups)
+    {
+      /* Check that the authz file did not contain any groups. */
+      if (0 != apr_hash_count(cb->parsed_groups))
+          return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+                                  ("Authz file cannot contain any groups"
+                                   " when global groups are being used."));
+
+      apr_hash_clear(cb->sections);
+      cb->parsing_groups = TRUE;
+      SVN_ERR(svn_config__parse_stream(groups,
+                                       svn_config__constructor_create(
+                                           groups_open_section,
+                                           close_section,
+                                           groups_add_value,
+                                           cb->parser_pool),
+                                       cb, cb->parser_pool));
+    }
+
+  /* Pass 2: Expand groups and construct the final svn_authz_t. */
+  cb->authz->groups = svn_hash__make(cb->authz->pool);
+  SVN_ERR(svn_iter_apr_hash(NULL, cb->parsed_groups,
+                            expand_group_callback, cb, cb->parser_pool));
+
+  cb->authz->acls = apr_array_make(cb->authz->pool, cb->parsed_acls->nelts,
+                                   sizeof(authz_acl_t));
+  cb->authz->global_rights = svn_hash__make(cb->authz->pool);
+  SVN_ERR(svn_iter_apr_array(NULL, cb->parsed_acls,
+                             expand_acl_callback, cb, cb->parser_pool));
+
+  *authz = cb->authz;
+  apr_pool_destroy(cb->parser_pool);
+  return SVN_NO_ERROR;
+}

Propchange: subversion/branches/authzperf/subversion/libsvn_repos/authz_parse.c
------------------------------------------------------------------------------
    svn:eol-style = native

Added: subversion/branches/authzperf/subversion/tests/libsvn_repos/authz-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_repos/authz-test.c?rev=1615480&view=auto
==============================================================================
--- subversion/branches/authzperf/subversion/tests/libsvn_repos/authz-test.c (added)
+++ subversion/branches/authzperf/subversion/tests/libsvn_repos/authz-test.c Mon Aug  4 03:14:15 2014
@@ -0,0 +1,164 @@
+/* authz-test.c --- tests for authorization system
+ *
+ * ====================================================================
+ *    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.
+ * ====================================================================
+ */
+
+#include "svn_pools.h"
+#include "svn_iter.h"
+
+#include "../../libsvn_repos/authz.h"
+
+#include "../svn_test.h"
+
+
+static svn_error_t *
+print_group_member(void *baton,
+                   const void *key, apr_ssize_t klen, void *val,
+                   apr_pool_t *pool)
+{
+  svn_boolean_t *first = baton;
+  const char *member = key;
+  printf("%s%s", (*first ? "" : ", "), member);
+  *first = FALSE;
+  return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+print_group(void *baton,
+            const void *key, apr_ssize_t klen, void *val,
+            apr_pool_t *pool)
+{
+  const char *group = key;
+  apr_hash_t *members = val;
+  svn_boolean_t first = TRUE;
+  svn_error_t *err;
+
+  printf("   %s = ", group);
+  err = svn_iter_apr_hash(NULL, members,
+                          print_group_member, &first, pool);
+  printf("\n");
+
+  return err;
+}
+
+
+static const char *
+access_string(svn_repos_authz_access_t access)
+{
+  switch (access & (svn_authz_read | svn_authz_write))
+    {
+    case svn_authz_none: return ""; break;
+    case svn_authz_read: return "r"; break;
+    case svn_authz_write: return "w"; break;
+    default:
+      return "rw";
+    }
+}
+
+
+static svn_error_t *
+test_authz_parse_tng(const svn_test_opts_t *opts,
+                     apr_pool_t *pool)
+{
+  const char *srcdir;
+  const char *rules_path;
+  apr_file_t *rules_file;
+  svn_stream_t *rules;
+  const char *groups_path;
+  apr_file_t *groups_file;
+  svn_stream_t *groups;
+  svn_authz_tng_t *authz;
+  int i;
+
+  SVN_ERR(svn_test_get_srcdir(&srcdir, opts, pool));
+  rules_path = svn_dirent_join(srcdir, "authz.rules", pool);
+  groups_path = svn_dirent_join(srcdir, "authz.groups", pool);
+
+  SVN_ERR(svn_io_file_open(&rules_file, rules_path,
+                           APR_READ, APR_OS_DEFAULT,
+                           pool));
+  rules = svn_stream_from_aprfile2(rules_file, FALSE, pool);
+  SVN_ERR(svn_io_file_open(&groups_file, groups_path,
+                           APR_READ, APR_OS_DEFAULT,
+                           pool));
+  groups = svn_stream_from_aprfile2(groups_file, FALSE, pool);
+  SVN_ERR(svn_authz__tng_parse(&authz, rules, groups, pool, pool));
+
+  printf("[rules]\n");
+  for (i = 0; i < authz->acls->nelts; ++i)
+    {
+      authz_acl_t *acl = &APR_ARRAY_IDX(authz->acls, i, authz_acl_t);
+      const svn_repos_authz_access_t all_access =
+        (acl->anon_access & acl->authn_access);
+      int j;
+
+      printf("   Sequence:   %ld\n"
+             "   Repository: [%s]\n"
+             "   Rule:  %s[/%s]\n",
+             (long)acl->sequence_number,
+             acl->repos,
+             (acl->glob ? "glob:" : "     "),
+             acl->rule);
+
+      if (acl->has_anon_access && acl->has_authn_access
+          && all_access != svn_authz_none)
+        printf("       * = %s\n", access_string(all_access));
+
+      if (acl->has_anon_access
+          && (acl->anon_access & ~all_access) != svn_authz_none)
+        printf("       $anonymous = %s\n",
+               access_string(acl->anon_access & ~all_access));
+
+      if (acl->has_authn_access
+          && (acl->authn_access & ~all_access) != svn_authz_none)
+        printf("       $authenticated = %s\n",
+               access_string(acl->authn_access & ~all_access));
+
+      for (j = 0; j < acl->user_access->nelts; ++j)
+        {
+          authz_ace_t *ace = &APR_ARRAY_IDX(acl->user_access, j, authz_ace_t);
+          printf("      %c%s = %s\n",
+                 (ace->inverted ? '~' : ' '),
+                 ace->name, access_string(ace->access));
+        }
+      printf("\n\n");
+    }
+
+  printf("[groups]\n");
+  SVN_ERR(svn_iter_apr_hash(NULL, authz->groups,
+                            print_group, NULL, pool));
+  printf("\n\n");
+
+  return SVN_NO_ERROR;
+}
+
+
+static int max_threads = 4;
+
+static struct svn_test_descriptor_t test_funcs[] =
+  {
+    SVN_TEST_NULL,
+    SVN_TEST_OPTS_PASS(test_authz_parse_tng,
+                       "test svn_authz__tng_parse"),
+    SVN_TEST_NULL
+  };
+
+SVN_TEST_MAIN

Added: subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.groups
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.groups?rev=1615480&view=auto
==============================================================================
--- subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.groups (added)
+++ subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.groups Mon Aug  4 03:14:15 2014
@@ -0,0 +1,4 @@
+[groups]
+x = a
+y = b, @z
+z = c, @x

Propchange: subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.groups
------------------------------------------------------------------------------
    svn:eol-style = LF

Added: subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.rules
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.rules?rev=1615480&view=auto
==============================================================================
--- subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.rules (added)
+++ subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.rules Mon Aug  4 03:14:15 2014
@@ -0,0 +1,19 @@
+[/]
+$anonymous = r
+$authenticated = rw
+
+[:glob:/]
+&x = r
+luser = w
+
+[bloop:/]
+@x = r
+@y = w
+
+[:glob:bloop:/]
+luser = rw
+~luser =
+~&x = w
+
+[aliases]
+x = luser

Propchange: subversion/branches/authzperf/subversion/tests/libsvn_repos/authz.rules
------------------------------------------------------------------------------
    svn:eol-style = CRLF