| /* authz.c : path-based access control |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2006 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| /*** Includes. ***/ |
| |
| #include <assert.h> |
| |
| #include <apr_pools.h> |
| #include <apr_file_io.h> |
| |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_path.h" |
| #include "svn_repos.h" |
| #include "svn_config.h" |
| #include "svn_ctype.h" |
| |
| |
| /*** Structures. ***/ |
| |
| /* Information for the config enumerators called during authz |
| lookup. */ |
| struct authz_lookup_baton { |
| /* The authz configuration. */ |
| svn_config_t *config; |
| |
| /* The user to authorize. */ |
| const char *user; |
| |
| /* Explicitly granted rights. */ |
| svn_repos_authz_access_t allow; |
| /* Explicitly denied rights. */ |
| svn_repos_authz_access_t deny; |
| |
| /* The rights required by the caller of the lookup. */ |
| svn_repos_authz_access_t required_access; |
| |
| /* The following are used exclusively in recursive lookups. */ |
| |
| /* The path in the repository to authorize. */ |
| const char *repos_path; |
| /* repos_path prefixed by the repository name. */ |
| const char *qualified_repos_path; |
| |
| /* Whether, at the end of a recursive lookup, access is granted. */ |
| svn_boolean_t access; |
| }; |
| |
| /* Information for the config enumeration functions called during the |
| validation process. */ |
| struct authz_validate_baton { |
| svn_config_t *config; /* The configuration file being validated. */ |
| svn_error_t *err; /* The error being thrown out of the |
| enumerator, if any. */ |
| }; |
| |
| /* Currently this structure is just a wrapper around a |
| svn_config_t. */ |
| struct svn_authz_t |
| { |
| svn_config_t *cfg; |
| }; |
| |
| |
| |
| /*** Checking access. ***/ |
| |
| /* Determine whether the REQUIRED access is granted given what authz |
| * to ALLOW or DENY. Return TRUE if the REQUIRED access is |
| * granted. |
| * |
| * Access is granted either when no required access is explicitly |
| * denied (implicit grant), or when the required access is explicitly |
| * granted, overriding any denials. |
| */ |
| static svn_boolean_t |
| authz_access_is_granted(svn_repos_authz_access_t allow, |
| svn_repos_authz_access_t deny, |
| svn_repos_authz_access_t required) |
| { |
| svn_repos_authz_access_t stripped_req = |
| required & (svn_authz_read | svn_authz_write); |
| |
| if ((deny & required) == svn_authz_none) |
| return TRUE; |
| else if ((allow & required) == stripped_req) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| |
| /* Decide whether the REQUIRED access has been conclusively |
| * determined. Return TRUE if the given ALLOW/DENY authz are |
| * conclusive regarding the REQUIRED authz. |
| * |
| * Conclusive determination occurs when any of the REQUIRED authz are |
| * granted or denied by ALLOW/DENY. |
| */ |
| static svn_boolean_t |
| authz_access_is_determined(svn_repos_authz_access_t allow, |
| svn_repos_authz_access_t deny, |
| svn_repos_authz_access_t required) |
| { |
| if ((deny & required) || (allow & required)) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| /* Return TRUE is USER equals ALIAS. The alias definitions are in the |
| "aliases" sections of CFG. Use POOL for temporary allocations during |
| the lookup. */ |
| static svn_boolean_t |
| authz_alias_is_user(svn_config_t *cfg, |
| const char *alias, |
| const char *user, |
| apr_pool_t *pool) |
| { |
| const char *value; |
| |
| svn_config_get(cfg, &value, "aliases", alias, NULL); |
| if (!value) |
| return FALSE; |
| |
| if (strcmp(value, user) == 0) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| |
| /* Return TRUE if USER is in GROUP. The group definitions are in the |
| "groups" section of CFG. Use POOL for temporary allocations during |
| the lookup. */ |
| static svn_boolean_t |
| authz_group_contains_user(svn_config_t *cfg, |
| const char *group, |
| const char *user, |
| apr_pool_t *pool) |
| { |
| const char *value; |
| apr_array_header_t *list; |
| int i; |
| |
| svn_config_get(cfg, &value, "groups", group, NULL); |
| |
| list = svn_cstring_split(value, ",", TRUE, pool); |
| |
| for (i = 0; i < list->nelts; i++) |
| { |
| const char *group_user = APR_ARRAY_IDX(list, i, char *); |
| |
| /* If the 'user' is a subgroup, recurse into it. */ |
| if (*group_user == '@') |
| { |
| if (authz_group_contains_user(cfg, &group_user[1], |
| user, pool)) |
| return TRUE; |
| } |
| |
| /* If the 'user' is an alias, verify it. */ |
| else if (*group_user == '&') |
| { |
| if (authz_alias_is_user(cfg, &group_user[1], |
| user, pool)) |
| return TRUE; |
| } |
| |
| /* If the user matches, stop. */ |
| else if (strcmp(user, group_user) == 0) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| |
| /* Determines whether an authz rule applies to the current |
| * user, given the name part of the rule's name-value pair |
| * in RULE_MATCH_STRING and the authz_lookup_baton object |
| * B with the username in question. |
| */ |
| static svn_boolean_t |
| authz_line_applies_to_user(const char *rule_match_string, |
| struct authz_lookup_baton *b, |
| apr_pool_t *pool) |
| { |
| /* If the rule has an inversion, recurse and invert the result. */ |
| if (rule_match_string[0] == '~') |
| return !authz_line_applies_to_user(&rule_match_string[1], b, pool); |
| |
| /* Check for special tokens. */ |
| if (strcmp(rule_match_string, "$anonymous") == 0) |
| return (b->user == NULL); |
| if (strcmp(rule_match_string, "$authenticated") == 0) |
| return (b->user != NULL); |
| |
| /* Check for a wildcard rule. */ |
| if (strcmp(rule_match_string, "*") == 0) |
| return TRUE; |
| |
| /* If we get here, then the rule is: |
| * - Not an inversion rule. |
| * - Not an authz token rule. |
| * - Not a wildcard rule. |
| * |
| * All that's left over is regular user or group specifications. |
| */ |
| |
| /* If the session is anonymous, then a user/group |
| * rule definitely won't match. |
| */ |
| if (b->user == NULL) |
| return FALSE; |
| |
| /* Process the rule depending on whether it is |
| * a user, alias or group rule. |
| */ |
| if (rule_match_string[0] == '@') |
| return authz_group_contains_user( |
| b->config, &rule_match_string[1], b->user, pool); |
| else if (rule_match_string[0] == '&') |
| return authz_alias_is_user( |
| b->config, &rule_match_string[1], b->user, pool); |
| else |
| return (strcmp(b->user, rule_match_string) == 0); |
| } |
| |
| |
| /* Callback to parse one line of an authz file and update the |
| * authz_baton accordingly. |
| */ |
| static svn_boolean_t |
| authz_parse_line(const char *name, const char *value, |
| void *baton, apr_pool_t *pool) |
| { |
| struct authz_lookup_baton *b = baton; |
| |
| /* Stop if the rule doesn't apply to this user. */ |
| if (!authz_line_applies_to_user(name, b, pool)) |
| return TRUE; |
| |
| /* Set the access grants for the rule. */ |
| if (strchr(value, 'r')) |
| b->allow |= svn_authz_read; |
| else |
| b->deny |= svn_authz_read; |
| |
| if (strchr(value, 'w')) |
| b->allow |= svn_authz_write; |
| else |
| b->deny |= svn_authz_write; |
| |
| return TRUE; |
| } |
| |
| |
| /* Callback to parse a section and update the authz_baton if the |
| * section denies access to the subtree the baton describes. |
| */ |
| static svn_boolean_t |
| authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool) |
| { |
| struct authz_lookup_baton *b = baton; |
| svn_boolean_t conclusive; |
| |
| /* Does the section apply to us? */ |
| if (svn_path_is_ancestor(b->qualified_repos_path, |
| section_name) == FALSE |
| && svn_path_is_ancestor(b->repos_path, |
| section_name) == FALSE) |
| return TRUE; |
| |
| /* Work out what this section grants. */ |
| b->allow = b->deny = 0; |
| svn_config_enumerate2(b->config, section_name, |
| authz_parse_line, b, pool); |
| |
| /* Has the section explicitly determined an access? */ |
| conclusive = authz_access_is_determined(b->allow, b->deny, |
| b->required_access); |
| |
| /* Is access granted OR inconclusive? */ |
| b->access = authz_access_is_granted(b->allow, b->deny, |
| b->required_access) |
| || !conclusive; |
| |
| /* As long as access isn't conclusively denied, carry on. */ |
| return b->access; |
| } |
| |
| |
| /* Validate access to the given user for the given path. This |
| * function checks rules for exactly the given path, and first tries |
| * to access a section specific to the given repository before falling |
| * back to pan-repository rules. |
| * |
| * Update *access_granted to inform the caller of the outcome of the |
| * lookup. Return a boolean indicating whether the access rights were |
| * successfully determined. |
| */ |
| static svn_boolean_t |
| authz_get_path_access(svn_config_t *cfg, const char *repos_name, |
| const char *path, const char *user, |
| svn_repos_authz_access_t required_access, |
| svn_boolean_t *access_granted, |
| apr_pool_t *pool) |
| { |
| const char *qualified_path; |
| struct authz_lookup_baton baton = { 0 }; |
| |
| baton.config = cfg; |
| baton.user = user; |
| |
| /* Try to locate a repository-specific block first. */ |
| qualified_path = apr_pstrcat(pool, repos_name, ":", path, NULL); |
| svn_config_enumerate2(cfg, qualified_path, |
| authz_parse_line, &baton, pool); |
| |
| *access_granted = authz_access_is_granted(baton.allow, baton.deny, |
| required_access); |
| |
| /* If the first test has determined access, stop now. */ |
| if (authz_access_is_determined(baton.allow, baton.deny, |
| required_access)) |
| return TRUE; |
| |
| /* No repository specific rule, try pan-repository rules. */ |
| svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool); |
| |
| *access_granted = authz_access_is_granted(baton.allow, baton.deny, |
| required_access); |
| return authz_access_is_determined(baton.allow, baton.deny, |
| required_access); |
| } |
| |
| |
| /* Validate access to the given user for the subtree starting at the |
| * given path. This function walks the whole authz file in search of |
| * rules applying to paths in the requested subtree which deny the |
| * requested access. |
| * |
| * As soon as one is found, or else when the whole ACL file has been |
| * searched, return the updated authorization status. |
| */ |
| static svn_boolean_t |
| authz_get_tree_access(svn_config_t *cfg, const char *repos_name, |
| const char *path, const char *user, |
| svn_repos_authz_access_t required_access, |
| apr_pool_t *pool) |
| { |
| struct authz_lookup_baton baton = { 0 }; |
| |
| baton.config = cfg; |
| baton.user = user; |
| baton.required_access = required_access; |
| baton.repos_path = path; |
| baton.qualified_repos_path = apr_pstrcat(pool, repos_name, |
| ":", path, NULL); |
| /* Default to access granted if no rules say otherwise. */ |
| baton.access = TRUE; |
| |
| svn_config_enumerate_sections2(cfg, authz_parse_section, |
| &baton, pool); |
| |
| return baton.access; |
| } |
| |
| |
| /* Callback to parse sections of the configuration file, looking for |
| any kind of granted access. Implements the |
| svn_config_section_enumerator2_t interface. */ |
| static svn_boolean_t |
| authz_global_parse_section(const char *section_name, void *baton, |
| apr_pool_t *pool) |
| { |
| struct authz_lookup_baton *b = baton; |
| |
| /* Does the section apply to the query? */ |
| if (section_name[0] == '/' |
| || strncmp(section_name, b->repos_path, |
| strlen(b->repos_path)) == 0) |
| { |
| b->allow = b->deny = svn_authz_none; |
| |
| svn_config_enumerate2(b->config, section_name, |
| authz_parse_line, baton, pool); |
| b->access = authz_access_is_granted(b->allow, b->deny, |
| b->required_access); |
| |
| /* Continue as long as we don't find a determined, granted access. */ |
| return !(b->access |
| && authz_access_is_determined(b->allow, b->deny, |
| b->required_access)); |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS |
| * to any path within the REPOSITORY. Return TRUE if so. Use POOL |
| * for temporary allocations. */ |
| static svn_boolean_t |
| authz_get_global_access(svn_config_t *cfg, const char *repos_name, |
| const char *user, |
| svn_repos_authz_access_t required_access, |
| apr_pool_t *pool) |
| { |
| struct authz_lookup_baton baton = { 0 }; |
| |
| baton.config = cfg; |
| baton.user = user; |
| baton.required_access = required_access; |
| baton.access = FALSE; /* Deny access by default. */ |
| baton.repos_path = apr_pstrcat(pool, repos_name, ":/", NULL); |
| |
| svn_config_enumerate_sections2(cfg, authz_global_parse_section, |
| &baton, pool); |
| |
| /* If walking the configuration was inconclusive, deny access. */ |
| if (!authz_access_is_determined(baton.allow, |
| baton.deny, baton.required_access)) |
| return FALSE; |
| |
| return baton.access; |
| } |
| |
| |
| |
| /*** Validating the authz file. ***/ |
| |
| /* Check for errors in GROUP's definition of CFG. The errors |
| * detected are references to non-existent groups and circular |
| * dependencies between groups. If an error is found, return |
| * SVN_ERR_AUTHZ_INVALID_CONFIG. Use POOL for temporary |
| * allocations only. |
| * |
| * CHECKED_GROUPS should be an empty (it is used for recursive calls). |
| */ |
| static svn_error_t * |
| authz_group_walk(svn_config_t *cfg, |
| const char *group, |
| apr_hash_t *checked_groups, |
| apr_pool_t *pool) |
| { |
| const char *value; |
| apr_array_header_t *list; |
| int i; |
| |
| svn_config_get(cfg, &value, "groups", group, NULL); |
| /* Having a non-existent group in the ACL configuration might be the |
| sign of a typo. Refuse to perform authz on uncertain rules. */ |
| if (!value) |
| return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "An authz rule refers to group '%s', " |
| "which is undefined", |
| group); |
| |
| list = svn_cstring_split(value, ",", TRUE, pool); |
| |
| for (i = 0; i < list->nelts; i++) |
| { |
| const char *group_user = APR_ARRAY_IDX(list, i, char *); |
| |
| /* If the 'user' is a subgroup, recurse into it. */ |
| if (*group_user == '@') |
| { |
| /* A circular dependency between groups is a Bad Thing. We |
| don't do authz with invalid ACL files. */ |
| if (apr_hash_get(checked_groups, &group_user[1], |
| APR_HASH_KEY_STRING)) |
| return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, |
| NULL, |
| "Circular dependency between " |
| "groups '%s' and '%s'", |
| &group_user[1], group); |
| |
| /* Add group to hash of checked groups. */ |
| apr_hash_set(checked_groups, &group_user[1], |
| APR_HASH_KEY_STRING, ""); |
| |
| /* Recurse on that group. */ |
| SVN_ERR(authz_group_walk(cfg, &group_user[1], |
| checked_groups, pool)); |
| |
| /* Remove group from hash of checked groups, so that we don't |
| incorrectly report an error if we see it again as part of |
| another group. */ |
| apr_hash_set(checked_groups, &group_user[1], |
| APR_HASH_KEY_STRING, NULL); |
| } |
| else if (*group_user == '&') |
| { |
| const char *alias; |
| |
| svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL); |
| /* Having a non-existent alias in the ACL configuration might be the |
| sign of a typo. Refuse to perform authz on uncertain rules. */ |
| if (!alias) |
| return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "An authz rule refers to alias '%s', " |
| "which is undefined", |
| &group_user[1]); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Callback to perform some simple sanity checks on an authz rule. |
| * |
| * - If RULE_MATCH_STRING references a group or an alias, verify that |
| * the group or alias definition exists. |
| * - If RULE_MATCH_STRING specifies a token (starts with $), verify |
| * that the token name is valid. |
| * - If RULE_MATCH_STRING is using inversion, verify that it isn't |
| * doing it more than once within the one rule, and that it isn't |
| * "~*", as that would never match. |
| * - Check that VALUE part of the rule specifies only allowed rule |
| * flag characters ('r' and 'w'). |
| * |
| * Return TRUE if the rule has no errors. Use BATON for context and |
| * error reporting. |
| */ |
| static svn_boolean_t authz_validate_rule(const char *rule_match_string, |
| const char *value, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| const char *val; |
| const char *match = rule_match_string; |
| struct authz_validate_baton *b = baton; |
| |
| /* Make sure the user isn't using double-negatives. */ |
| if (match[0] == '~') |
| { |
| /* Bump the pointer past the inversion for the other checks. */ |
| match++; |
| |
| /* Another inversion is a double negative; we can't not stop. */ |
| if (match[0] == '~') |
| { |
| b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "Rule '%s' has more than one " |
| "inversion; double negatives are " |
| "not permitted.", |
| rule_match_string); |
| return FALSE; |
| } |
| |
| /* Make sure that the rule isn't "~*", which won't ever match. */ |
| if (strcmp(match, "*") == 0) |
| { |
| b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "Authz rules with match string '~*' " |
| "are not allowed, because they never " |
| "match anyone."); |
| return FALSE; |
| } |
| } |
| |
| /* If the rule applies to a group, check its existence. */ |
| if (match[0] == '@') |
| { |
| const char *group = &match[1]; |
| |
| svn_config_get(b->config, &val, "groups", group, NULL); |
| |
| /* Having a non-existent group in the ACL configuration might be |
| the sign of a typo. Refuse to perform authz on uncertain |
| rules. */ |
| if (!val) |
| { |
| b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "An authz rule refers to group " |
| "'%s', which is undefined", |
| rule_match_string); |
| return FALSE; |
| } |
| } |
| |
| /* If the rule applies to an alias, check its existence. */ |
| if (match[0] == '&') |
| { |
| const char *alias = &match[1]; |
| |
| svn_config_get(b->config, &val, "aliases", alias, NULL); |
| |
| if (!val) |
| { |
| b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "An authz rule refers to alias " |
| "'%s', which is undefined", |
| rule_match_string); |
| return FALSE; |
| } |
| } |
| |
| /* If the rule specifies a token, check its validity. */ |
| if (match[0] == '$') |
| { |
| const char *token_name = &match[1]; |
| |
| if ((strcmp(token_name, "anonymous") != 0) |
| && (strcmp(token_name, "authenticated") != 0)) |
| { |
| b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "Unrecognized authz token '%s'.", |
| rule_match_string); |
| return FALSE; |
| } |
| } |
| |
| val = value; |
| |
| while (*val) |
| { |
| if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val)) |
| { |
| b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, |
| "The character '%c' in rule '%s' is not " |
| "allowed in authz rules", *val, |
| rule_match_string); |
| return FALSE; |
| } |
| |
| ++val; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Callback to check ALIAS's definition for validity. Use |
| BATON for context and error reporting. */ |
| static svn_boolean_t authz_validate_alias(const char *alias, |
| const char *value, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| /* No checking at the moment, every alias is valid */ |
| return TRUE; |
| } |
| |
| |
| /* Callback to check GROUP's definition for cyclic dependancies. Use |
| BATON for context and error reporting. */ |
| static svn_boolean_t authz_validate_group(const char *group, |
| const char *value, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| struct authz_validate_baton *b = baton; |
| |
| b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool); |
| if (b->err) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| |
| /* Callback to check the contents of the configuration section given |
| by NAME. Use BATON for context and error reporting. */ |
| static svn_boolean_t authz_validate_section(const char *name, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| struct authz_validate_baton *b = baton; |
| |
| /* If the section is the groups definition, use the group checking |
| callback. Otherwise, use the rule checking callback. */ |
| if (strncmp(name, "groups", 6) == 0) |
| svn_config_enumerate2(b->config, name, authz_validate_group, |
| baton, pool); |
| else if (strncmp(name, "aliases", 7) == 0) |
| svn_config_enumerate2(b->config, name, authz_validate_alias, |
| baton, pool); |
| else |
| svn_config_enumerate2(b->config, name, authz_validate_rule, |
| baton, pool); |
| |
| if (b->err) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| |
| |
| /*** Public functions. ***/ |
| |
| svn_error_t * |
| svn_repos_authz_read(svn_authz_t **authz_p, const char *file, |
| svn_boolean_t must_exist, apr_pool_t *pool) |
| { |
| svn_authz_t *authz = apr_palloc(pool, sizeof(*authz)); |
| struct authz_validate_baton baton = { 0 }; |
| |
| baton.err = SVN_NO_ERROR; |
| |
| /* Load the rule file. */ |
| SVN_ERR(svn_config_read(&authz->cfg, file, must_exist, pool)); |
| baton.config = authz->cfg; |
| |
| /* Step through the entire rule file, stopping on error. */ |
| svn_config_enumerate_sections2(authz->cfg, authz_validate_section, |
| &baton, pool); |
| SVN_ERR(baton.err); |
| |
| *authz_p = authz; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name, |
| const char *path, const char *user, |
| svn_repos_authz_access_t required_access, |
| svn_boolean_t *access_granted, |
| apr_pool_t *pool) |
| { |
| const char *current_path = path; |
| |
| /* If PATH is NULL, do a global access lookup. */ |
| if (!path) |
| { |
| *access_granted = authz_get_global_access(authz->cfg, repos_name, |
| user, required_access, |
| pool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Determine the granted access for the requested path. */ |
| while (!authz_get_path_access(authz->cfg, repos_name, |
| current_path, user, |
| required_access, |
| access_granted, |
| pool)) |
| { |
| /* Stop if the loop hits the repository root with no |
| results. */ |
| if (current_path[0] == '/' && current_path[1] == '\0') |
| { |
| /* Deny access by default. */ |
| *access_granted = FALSE; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Work back to the parent path. */ |
| svn_path_split(current_path, ¤t_path, NULL, pool); |
| } |
| |
| /* If the caller requested recursive access, we need to walk through |
| the entire authz config to see whether any child paths are denied |
| to the requested user. */ |
| if (*access_granted && (required_access & svn_authz_recursive)) |
| *access_granted = authz_get_tree_access(authz->cfg, repos_name, path, |
| user, required_access, pool); |
| |
| return SVN_NO_ERROR; |
| } |