1 /* authz.c : path-based access control
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
28 #include <apr_fnmatch.h>
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_dirent_uri.h"
35 #include "svn_repos.h"
36 #include "svn_config.h"
37 #include "svn_ctype.h"
38 #include "private/svn_atomic.h"
39 #include "private/svn_fspath.h"
40 #include "private/svn_repos_private.h"
41 #include "private/svn_sorts_private.h"
42 #include "private/svn_subr_private.h"
45 #include "config_file.h"
48 /*** Access rights. ***/
50 /* This structure describes the access rights given to a specific user by
51 * a path rule (actually the rule set specified for a path). I.e. there is
52 * one instance of this per path rule.
54 typedef struct path_access_t
56 /* Sequence number of the path rule that this struct was derived from.
57 * If multiple rules apply to the same path (only possible with wildcard
58 * matching), the one with the highest SEQUENCE_NUMBER wins, i.e. the latest
59 * one defined in the authz file.
61 * A value of 0 denotes the default rule at the repository root denying
62 * access to everybody. User-defined path rules start with ID 1.
66 /* Access rights of the respective user as defined by the rule set. */
67 authz_access_t rights;
70 /* Use this to indicate that no sequence ID has been assigned.
71 * It will automatically be inferior to (less than) any other sequence ID. */
72 #define NO_SEQUENCE_NUMBER (-1)
74 /* Convenience structure combining the node-local access rights with the
75 * min and max rights granted within the sub-tree. */
76 typedef struct limited_rights_t
78 /* Access granted to the current user. If the SEQUENCE_NUMBER member is
79 * NO_SEQUENCE_NUMBER, there has been no specific path rule for this PATH
80 * but only for some sub-path(s). There is always a rule at the root node.
84 /* Minimal access rights that the user has on this or any other node in
85 * the sub-tree. This does not take inherited rights into account. */
86 authz_access_t min_rights;
88 /* Maximal access rights that the user has on this or any other node in
89 * the sub-tree. This does not take inherited rights into account. */
90 authz_access_t max_rights;
94 /* Return TRUE, if RIGHTS has local rights defined in the ACCESS member. */
96 has_local_rule(const limited_rights_t *rights)
98 return rights->access.sequence_number != NO_SEQUENCE_NUMBER;
101 /* Aggregate the ACCESS spec of TARGET and RIGHTS into TARGET. I.e. if both
102 * are specified, pick one in accordance to the precedence rules. */
104 combine_access(limited_rights_t *target,
105 const limited_rights_t *rights)
107 /* This implies the check for NO_SEQUENCE_NUMBER, i.e no rights being
109 if (target->access.sequence_number < rights->access.sequence_number)
110 target->access = rights->access;
113 /* Aggregate the min / max access rights of TARGET and RIGHTS into TARGET. */
115 combine_right_limits(limited_rights_t *target,
116 const limited_rights_t *rights)
118 target->max_rights |= rights->max_rights;
119 target->min_rights &= rights->min_rights;
124 /*** Authz cache access. ***/
126 /* All authz instances currently in use as well as all filtered authz
127 * instances in use will be cached here.
128 * Both caches will be instantiated at most once. */
129 static svn_object_pool__t *authz_pool = NULL;
130 static svn_object_pool__t *filtered_pool = NULL;
131 static svn_atomic_t authz_pool_initialized = FALSE;
133 /* Implements svn_atomic__err_init_func_t. */
135 synchronized_authz_initialize(void *baton, apr_pool_t *pool)
138 svn_boolean_t multi_threaded = TRUE;
140 svn_boolean_t multi_threaded = FALSE;
143 SVN_ERR(svn_object_pool__create(&authz_pool, multi_threaded, pool));
144 SVN_ERR(svn_object_pool__create(&filtered_pool, multi_threaded, pool));
150 svn_repos_authz_initialize(apr_pool_t *pool)
152 /* Protect against multiple calls. */
153 return svn_error_trace(svn_atomic__init_once(&authz_pool_initialized,
154 synchronized_authz_initialize,
158 /* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in RESULT_POOL.
159 * GROUPS_KEY may be NULL. This is the key for the AUTHZ_POOL.
161 static svn_membuf_t *
162 construct_authz_key(const svn_checksum_t *authz_key,
163 const svn_checksum_t *groups_key,
164 apr_pool_t *result_pool)
166 svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
169 apr_size_t authz_size = svn_checksum_size(authz_key);
170 apr_size_t groups_size = svn_checksum_size(groups_key);
172 svn_membuf__create(result, authz_size + groups_size, result_pool);
173 result->size = authz_size + groups_size; /* exact length is required! */
175 memcpy(result->data, authz_key->digest, authz_size);
176 memcpy((char *)result->data + authz_size,
177 groups_key->digest, groups_size);
181 apr_size_t size = svn_checksum_size(authz_key);
182 svn_membuf__create(result, size, result_pool);
183 result->size = size; /* exact length is required! */
184 memcpy(result->data, authz_key->digest, size);
190 /* Return a combination of REPOS_NAME, USER and AUTHZ_ID, allocated in
191 * RESULT_POOL. USER may be NULL. This is the key for the FILTERED_POOL.
193 static svn_membuf_t *
194 construct_filtered_key(const char *repos_name,
196 const svn_membuf_t *authz_id,
197 apr_pool_t *result_pool)
199 svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
200 size_t repos_len = strlen(repos_name);
201 size_t user_len = user ? strlen(user) : 1;
202 const char *nullable_user = user ? user : "\0";
203 size_t size = authz_id->size + repos_len + 1 + user_len + 1;
205 svn_membuf__create(result, size, result_pool);
208 memcpy(result->data, repos_name, repos_len + 1);
209 size = repos_len + 1;
210 memcpy((char *)result->data + size, nullable_user, user_len + 1);
211 size += user_len + 1;
212 memcpy((char *)result->data + size, authz_id->data, authz_id->size);
218 /*** Constructing the prefix tree. ***/
220 /* Since prefix arrays may have more than one hit, we need to link them
221 * for fast lookup. */
222 typedef struct sorted_pattern_t
224 /* The filtered tree node carrying the prefix. */
227 /* Entry that is a prefix to this one or NULL. */
228 struct sorted_pattern_t *next;
231 /* Substructure of node_t. It contains all sub-node that use patterns
232 * in the next segment level. We keep it separate to save a bit of memory
233 * and to be able to check for pattern presence in a single operation.
235 typedef struct node_pattern_t
237 /* If not NULL, this represents the "*" follow-segment. */
240 /* If not NULL, this represents the "**" follow-segment. */
241 struct node_t *any_var;
243 /* If not NULL, the segments of all sorted_pattern_t in this array are the
244 * prefix part of "prefix*" patterns. Sorted by segment prefix. */
245 apr_array_header_t *prefixes;
247 /* If not NULL, the segments of all sorted_pattern_t in this array are the
248 * reversed suffix part of "*suffix" patterns. Sorted by reversed
250 apr_array_header_t *suffixes;
252 /* If not NULL, the segments of all sorted_pattern_t in this array contain
253 * wildcards and don't fit into any of the above categories.
254 * The NEXT members of the elements will not be used. */
255 apr_array_header_t *complex;
257 /* This node itself is a "**" segment and must therefore itself be added
258 * to the matching node list for the next level. */
259 svn_boolean_t repeat;
262 /* The pattern tree. All relevant path rules are being folded into this
263 * prefix tree, with a single, whole segment stored at each node. The whole
264 * tree applies to a single user only.
266 typedef struct node_t
268 /* The segment as specified in the path rule. During the lookup tree walk,
269 * this will compared to the respective segment of the path to check. */
270 svn_string_t segment;
272 /* Immediate access rights granted by rules on this node and the min /
273 * max rights on any path in this sub-tree. */
274 limited_rights_t rights;
276 /* Map of sub-segment(const char *) to respective node (node_t) for all
277 * sub-segments that have rules on themselves or their respective subtrees.
278 * NULL, if there are no rules for sub-paths relevant to the user. */
279 apr_hash_t *sub_nodes;
281 /* If not NULL, this contains the pattern-based segment sub-nodes. */
282 node_pattern_t *pattern_sub_nodes;
285 /* Create a new tree node for SEGMENT.
286 Note: SEGMENT->pattern is always interned and therefore does not
287 have to be copied into the result pool. */
289 create_node(authz_rule_segment_t *segment,
290 apr_pool_t *result_pool)
292 node_t *result = apr_pcalloc(result_pool, sizeof(*result));
294 result->segment = segment->pattern;
297 result->segment.data = "";
298 result->segment.len = 0;
300 result->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
304 /* Auto-create a node in *NODE, make it apply to SEGMENT and return it. */
306 ensure_node(node_t **node,
307 authz_rule_segment_t *segment,
308 apr_pool_t *result_pool)
311 *node = create_node(segment, result_pool);
316 /* compare_func comparing segment names. It takes a sorted_pattern_t* as
317 * VOID_LHS and a const authz_rule_segment_t * as VOID_RHS.
320 compare_node_rule_segment(const void *void_lhs,
321 const void *void_rhs)
323 const sorted_pattern_t *element = void_lhs;
324 const authz_rule_segment_t *segment = void_rhs;
326 return strcmp(element->node->segment.data, segment->pattern.data);
329 /* compare_func comparing segment names. It takes a sorted_pattern_t* as
330 * VOID_LHS and a const char * as VOID_RHS.
333 compare_node_path_segment(const void *void_lhs,
334 const void *void_rhs)
336 const sorted_pattern_t *element = void_lhs;
337 const char *segment = void_rhs;
339 return strcmp(element->node->segment.data, segment);
342 /* Make sure a node_t* for SEGMENT exists in *ARRAY and return it.
343 * Auto-create either if they don't exist. Entries in *ARRAY are
344 * sorted by their segment strings.
347 ensure_node_in_array(apr_array_header_t **array,
348 authz_rule_segment_t *segment,
349 apr_pool_t *result_pool)
352 sorted_pattern_t entry;
353 sorted_pattern_t *entry_ptr;
355 /* Auto-create the array. */
357 *array = apr_array_make(result_pool, 4, sizeof(sorted_pattern_t));
359 /* Find the node in ARRAY and the IDX at which it were to be inserted.
360 * Initialize IDX such that we won't attempt a hinted lookup (likely
361 * to fail and therefore pure overhead). */
362 idx = (*array)->nelts;
363 entry_ptr = svn_sort__array_lookup(*array, segment, &idx,
364 compare_node_rule_segment);
366 return entry_ptr->node;
368 /* There is no such node, yet.
369 * Create one and insert it into the sorted array. */
370 entry.node = create_node(segment, result_pool);
372 svn_sort__array_insert(*array, &entry, idx);
377 /* Auto-create the PATTERN_SUB_NODES sub-structure in *NODE and return it. */
378 static node_pattern_t *
379 ensure_pattern_sub_nodes(node_t *node,
380 apr_pool_t *result_pool)
382 if (node->pattern_sub_nodes == NULL)
383 node->pattern_sub_nodes = apr_pcalloc(result_pool,
384 sizeof(*node->pattern_sub_nodes));
386 return node->pattern_sub_nodes;
389 /* Combine an ACL rule segment with the corresponding node in our filtered
391 typedef struct node_segment_pair_t
393 authz_rule_segment_t *segment;
395 } node_segment_pair_t;
397 /* Context object to be used with process_acl. It allows us to re-use
398 * information from previous insertions. */
399 typedef struct construction_context_t
401 /* Array of node_segment_pair_t. It contains all segments already
402 * processed of the current insertion together with the respective
403 * nodes in our filtered tree. Before the next lookup, the tree
404 * walk for the common prefix can be skipped. */
405 apr_array_header_t *path;
406 } construction_context_t;
408 /* Return a new context object allocated in RESULT_POOL. */
409 static construction_context_t *
410 create_construction_context(apr_pool_t *result_pool)
412 construction_context_t *result = apr_pcalloc(result_pool, sizeof(*result));
414 /* Array will be auto-extended but this initial size will make it rarely
416 result->path = apr_array_make(result_pool, 32, sizeof(node_segment_pair_t));
421 /* Constructor utility: Below NODE, recursively insert sub-nodes for the
422 * path given as *SEGMENTS of length SEGMENT_COUNT. If matching nodes
423 * already exist, use those instead of creating new ones. Set the leave
424 * node's access rights spec to PATH_ACCESS. Update the context info in CTX.
427 insert_path(construction_context_t *ctx,
429 path_access_t *path_access,
431 authz_rule_segment_t *segment,
432 apr_pool_t *result_pool,
433 apr_pool_t *scratch_pool)
436 node_segment_pair_t *node_segment;
439 if (segment_count == 0)
441 /* Set access rights. Note that there might be multiple rules for
442 * the same path due to non-repo-specific rules vs. repo-specific
443 * ones. Whichever gets defined last wins.
445 limited_rights_t rights;
446 rights.access = *path_access;
447 rights.max_rights = path_access->rights;
448 rights.min_rights = path_access->rights;
449 combine_access(&node->rights, &rights);
453 /* Any wildcards? They will go into a separate sub-structure. */
454 if (segment->kind != authz_rule_literal)
455 ensure_pattern_sub_nodes(node, result_pool);
457 switch (segment->kind)
459 /* A full wildcard segment? */
460 case authz_rule_any_segment:
461 sub_node = ensure_node(&node->pattern_sub_nodes->any,
462 segment, result_pool);
465 /* One or more full wildcard segments? */
466 case authz_rule_any_recursive:
467 sub_node = ensure_node(&node->pattern_sub_nodes->any_var,
468 segment, result_pool);
469 ensure_pattern_sub_nodes(sub_node, result_pool)->repeat = TRUE;
472 /* A single wildcard at the end of the segment? */
473 case authz_rule_prefix:
474 sub_node = ensure_node_in_array(&node->pattern_sub_nodes->prefixes,
475 segment, result_pool);
478 /* A single wildcard at the start of segments? */
479 case authz_rule_suffix:
480 sub_node = ensure_node_in_array(&node->pattern_sub_nodes->suffixes,
481 segment, result_pool);
484 /* General pattern? */
485 case authz_rule_fnmatch:
486 sub_node = ensure_node_in_array(&node->pattern_sub_nodes->complex,
487 segment, result_pool);
490 /* Then it must be a literal. */
492 SVN_ERR_ASSERT_NO_RETURN(segment->kind == authz_rule_literal);
494 if (!node->sub_nodes)
496 node->sub_nodes = svn_hash__make(result_pool);
501 sub_node = svn_hash_gets(node->sub_nodes, segment->pattern.data);
504 /* Auto-insert a sub-node for the current segment. */
507 sub_node = create_node(segment, result_pool);
508 apr_hash_set(node->sub_nodes,
509 sub_node->segment.data,
510 sub_node->segment.len,
515 /* Update context. */
516 node_segment = apr_array_push(ctx->path);
517 node_segment->segment = segment;
518 node_segment->node = sub_node;
520 /* Continue at the sub-node with the next segment. */
521 insert_path(ctx, sub_node, path_access, segment_count - 1, segment + 1,
522 result_pool, scratch_pool);
526 /* If the ACL is relevant to the REPOSITORY and user (given as MEMBERSHIPS
527 * plus ANONYMOUS flag), insert the respective nodes into tree starting
528 * at ROOT. Use the context info of the previous call in CTX to eliminate
529 * repeated lookups. Allocate new nodes in RESULT_POOL and use SCRATCH_POOL
530 * for temporary allocations.
533 process_acl(construction_context_t *ctx,
534 const authz_acl_t *acl,
536 const char *repository,
538 apr_pool_t *result_pool,
539 apr_pool_t *scratch_pool)
541 path_access_t path_access;
545 /* Skip ACLs that don't say anything about the current user
546 and/or repository. */
547 if (!svn_authz__get_acl_access(&path_access.rights, acl, user, repository))
550 /* Insert the rule into the filtered tree. */
551 path_access.sequence_number = acl->sequence_number;
553 /* Try to reuse results from previous runs.
554 * Basically, skip the common prefix. */
556 for (i = 0; i < ctx->path->nelts; ++i)
558 const node_segment_pair_t *step
559 = &APR_ARRAY_IDX(ctx->path, i, const node_segment_pair_t);
561 /* Exploit the fact that all strings in the authz model are unique /
562 * internized and can be identified by address alone. */
564 || i >= acl->rule.len
565 || step->segment->kind != acl->rule.path[i].kind
566 || step->segment->pattern.data != acl->rule.path[i].pattern.data)
568 ctx->path->nelts = i;
577 /* Insert the path rule into the filtered tree. */
578 insert_path(ctx, node, &path_access,
579 acl->rule.len - i, acl->rule.path + i,
580 result_pool, scratch_pool);
583 /* Forward declaration ... */
585 trim_tree(node_t *node,
587 apr_pool_t *scratch_pool);
589 /* Call trim_tree() with LATEST_ANY_VAR on all elements in the *HASH of
590 * node_t * and remove empty nodes from. *HASH may be NULL. If all nodes
591 * could be removed, set *HASH to NULL and return TRUE. Allocate temporary
592 * data in SCRATCH_POOL.
595 trim_subnode_hash(apr_hash_t **hash,
597 apr_pool_t *scratch_pool)
601 apr_array_header_t *to_remove = apr_array_make(scratch_pool, 0,
604 apr_hash_index_t *hi;
605 for (hi = apr_hash_first(scratch_pool, *hash);
607 hi = apr_hash_next(hi))
609 node_t *node = apr_hash_this_val(hi);
610 if (trim_tree(node, latest_any_var, scratch_pool))
611 APR_ARRAY_PUSH(to_remove, node_t *) = node;
614 /* Are some nodes left? */
615 if (to_remove->nelts < apr_hash_count(*hash))
617 /* Remove empty nodes (if any). */
619 for (i = 0; i < to_remove->nelts; ++i)
621 node_t *node = APR_ARRAY_IDX(to_remove, i, node_t *);
622 apr_hash_set(*hash, node->segment.data, node->segment.len,
629 /* No nodes left. A NULL hash is more efficient than an empty one. */
636 /* Call trim_tree() with LATEST_ANY_VAR on all elements in the *ARRAY of
637 * node_t * and remove empty nodes from. *ARRAY may be NULL. If all nodes
638 * could be removed, set *ARRAY to NULL and return TRUE. Allocate
639 * temporary data in SCRATCH_POOL.
642 trim_subnode_array(apr_array_header_t **array,
644 apr_pool_t *scratch_pool)
649 for (i = 0, dest = 0; i < (*array)->nelts; ++i)
651 node_t *node = APR_ARRAY_IDX(*array, i, sorted_pattern_t).node;
652 if (!trim_tree(node, latest_any_var, scratch_pool))
655 APR_ARRAY_IDX(*array, dest, sorted_pattern_t)
656 = APR_ARRAY_IDX(*array, i, sorted_pattern_t);
661 /* Are some nodes left? */
664 /* Trim it to the number of valid entries. */
665 (*array)->nelts = dest;
669 /* No nodes left. A NULL array is more efficient than an empty one. */
676 /* Remove all rules and sub-nodes from NODE that are fully eclipsed by the
677 * "any-var" rule with sequence number LATEST_ANY_VAR. Return TRUE, if
678 * there are no rules left in the sub-tree, including NODE.
679 * Allocate temporary data in SCRATCH_POOL.
682 trim_tree(node_t *node,
684 apr_pool_t *scratch_pool)
686 svn_boolean_t removed_all = TRUE;
688 /* For convenience, we allow NODE to be NULL: */
692 /* Do we have a later "any_var" rule at this node. */
693 if ( node->pattern_sub_nodes
694 && node->pattern_sub_nodes->any_var
695 && node->pattern_sub_nodes->any_var->rights.access.sequence_number
699 = node->pattern_sub_nodes->any_var->rights.access.sequence_number;
702 /* Is there a local rule at this node that is not eclipsed by any_var? */
703 if (has_local_rule(&node->rights))
705 /* Remove the local rule, if it got eclipsed.
706 * Note that for the latest any_var node, the sequence number is equal. */
707 if (node->rights.access.sequence_number >= latest_any_var)
710 node->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
713 /* Process all sub-nodes. */
714 removed_all &= trim_subnode_hash(&node->sub_nodes, latest_any_var,
717 if (node->pattern_sub_nodes)
719 if (trim_tree(node->pattern_sub_nodes->any, latest_any_var,
721 node->pattern_sub_nodes->any = NULL;
725 if (trim_tree(node->pattern_sub_nodes->any_var, latest_any_var,
727 node->pattern_sub_nodes->any_var = NULL;
731 removed_all &= trim_subnode_array(&node->pattern_sub_nodes->prefixes,
732 latest_any_var, scratch_pool);
733 removed_all &= trim_subnode_array(&node->pattern_sub_nodes->suffixes,
734 latest_any_var, scratch_pool);
735 removed_all &= trim_subnode_array(&node->pattern_sub_nodes->complex,
736 latest_any_var, scratch_pool);
738 /* Trim the tree as much as possible to speed up lookup(). */
740 node->pattern_sub_nodes = NULL;
746 /* Forward declaration ... */
748 finalize_tree(node_t *node,
749 limited_rights_t *sum,
750 apr_pool_t *scratch_pool);
752 /* Call finalize_tree() on all elements in the HASH of node_t *, passing
753 * SUM along. HASH may be NULL. Use SCRATCH_POOL for temporary allocations.
756 finalize_subnode_hash(apr_hash_t *hash,
757 limited_rights_t *sum,
758 apr_pool_t *scratch_pool)
762 apr_hash_index_t *hi;
763 for (hi = apr_hash_first(scratch_pool, hash);
765 hi = apr_hash_next(hi))
766 finalize_tree(apr_hash_this_val(hi), sum, scratch_pool);
770 /* Call finalize_up_tree() on all elements in the ARRAY of node_t *,
771 * passing SUM along. ARRAY may be NULL. Use SCRATCH_POOL for temporary
775 finalize_subnode_array(apr_array_header_t *array,
776 limited_rights_t *sum,
777 apr_pool_t *scratch_pool)
782 for (i = 0; i < array->nelts; ++i)
783 finalize_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node, sum,
788 /* Link prefixes within the sorted ARRAY. */
790 link_prefix_patterns(apr_array_header_t *array)
796 for (i = 1; i < array->nelts; ++i)
798 sorted_pattern_t *prev
799 = &APR_ARRAY_IDX(array, i - 1, sorted_pattern_t);
800 sorted_pattern_t *pattern
801 = &APR_ARRAY_IDX(array, i, sorted_pattern_t);
803 /* Does PATTERN potentially have a prefix in ARRAY?
804 * If so, at least the first char must match with the predecessor's
805 * because the array is sorted by that string. */
806 if (prev->node->segment.data[0] != pattern->node->segment.data[0])
809 /* Only the predecessor or any of its prefixes can be the closest
810 * prefix to PATTERN. */
811 for ( ; prev; prev = prev->next)
812 if ( prev->node->segment.len < pattern->node->segment.len
813 && !memcmp(prev->node->segment.data,
814 pattern->node->segment.data,
815 prev->node->segment.len))
817 pattern->next = prev;
823 /* Recursively finalization the tree node properties for NODE. Update SUM
824 * (of NODE's parent) by combining it with the recursive access rights info
825 * on NODE. Use SCRATCH_POOL for temporary allocations.
828 finalize_tree(node_t *node,
829 limited_rights_t *sum,
830 apr_pool_t *scratch_pool)
832 limited_rights_t *local_sum = &node->rights;
834 /* For convenience, we allow NODE to be NULL: */
838 /* Sum of rights at NODE - so far. */
839 if (has_local_rule(local_sum))
841 local_sum->max_rights = local_sum->access.rights;
842 local_sum->min_rights = local_sum->access.rights;
846 local_sum->min_rights = authz_access_write;
847 local_sum->max_rights = authz_access_none;
850 /* Process all sub-nodes. */
851 finalize_subnode_hash(node->sub_nodes, local_sum, scratch_pool);
853 if (node->pattern_sub_nodes)
855 finalize_tree(node->pattern_sub_nodes->any, local_sum, scratch_pool);
856 finalize_tree(node->pattern_sub_nodes->any_var, local_sum, scratch_pool);
858 finalize_subnode_array(node->pattern_sub_nodes->prefixes, local_sum,
860 finalize_subnode_array(node->pattern_sub_nodes->suffixes, local_sum,
862 finalize_subnode_array(node->pattern_sub_nodes->complex, local_sum,
865 /* Link up the prefixes / suffixes. */
866 link_prefix_patterns(node->pattern_sub_nodes->prefixes);
867 link_prefix_patterns(node->pattern_sub_nodes->suffixes);
870 /* Add our min / max info to the parent's info.
871 * Idempotent for parent == node (happens at root). */
872 combine_right_limits(sum, local_sum);
875 /* From the authz CONFIG, extract the parts relevant to USER and REPOSITORY.
876 * Return the filtered rule tree.
879 create_user_authz(authz_full_t *authz,
880 const char *repository,
882 apr_pool_t *result_pool,
883 apr_pool_t *scratch_pool)
886 node_t *root = create_node(NULL, result_pool);
887 construction_context_t *ctx = create_construction_context(scratch_pool);
889 /* Use a separate sub-pool to keep memory usage tight. */
890 apr_pool_t *subpool = svn_pool_create(scratch_pool);
892 /* Find all ACLs for REPOSITORY.
893 * Note that repo-specific rules replace global rules,
894 * even if they don't apply to the current user. */
895 apr_array_header_t *acls = apr_array_make(subpool, authz->acls->nelts,
896 sizeof(authz_acl_t *));
897 for (i = 0; i < authz->acls->nelts; ++i)
899 const authz_acl_t *acl = &APR_ARRAY_IDX(authz->acls, i, authz_acl_t);
900 if (svn_authz__acl_applies_to_repo(acl, repository))
902 /* ACLs in the AUTHZ are sorted by path and repository.
903 * So, if there is a rule for the repo and a global rule for the
904 * same path, we will detect them here. */
907 const authz_acl_t *prev_acl
908 = APR_ARRAY_IDX(acls, acls->nelts - 1, const authz_acl_t *);
909 if (svn_authz__compare_paths(&prev_acl->rule, &acl->rule) == 0)
911 SVN_ERR_ASSERT_NO_RETURN(!strcmp(prev_acl->rule.repos,
912 AUTHZ_ANY_REPOSITORY));
913 SVN_ERR_ASSERT_NO_RETURN(strcmp(acl->rule.repos,
914 AUTHZ_ANY_REPOSITORY));
919 APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
923 /* Filtering and tree construction. */
924 for (i = 0; i < acls->nelts; ++i)
925 process_acl(ctx, APR_ARRAY_IDX(acls, i, const authz_acl_t *),
926 root, repository, user, result_pool, subpool);
928 /* If there is no relevant rule at the root node, the "no access" default
929 * applies. Give it a SEQUENCE_NUMBER that will never overrule others. */
930 if (!has_local_rule(&root->rights))
932 root->rights.access.sequence_number = 0;
933 root->rights.access.rights = authz_access_none;
938 * We can't do pattern comparison, so for most pattern rules we cannot
939 * say that a set of rules "eclipses" / overrides a given other set of
940 * rules for all possible paths. That limits the accuracy of our check
941 * for recursive access in similar ways than for non-pattern rules.
943 * However, the user expects a rule ending with "**" to eclipse any older
944 * rule in that sub-tree recursively. So, this trim function removes
945 * eclipsed nodes from the tree.
947 svn_pool_clear(subpool);
948 trim_tree(root, NO_SEQUENCE_NUMBER, subpool);
950 /* Calculate recursive rights.
952 * This is a bottom-up calculation of the range of access rights
953 * specified anywhere in the respective sub-tree, including the base
956 * To prevent additional finalization passes, we piggy-back the addition
957 * of the ordering links of the prefix and suffix sub-node rules.
959 svn_pool_clear(subpool);
960 finalize_tree(root, &root->rights, subpool);
963 svn_pool_destroy(subpool);
970 /* Reusable lookup state object. It is easy to pass to functions and
971 * recycling it between lookups saves significant setup costs. */
972 typedef struct lookup_state_t
974 /* Rights immediately applying to this node and limits to the rights to
976 limited_rights_t rights;
978 /* Nodes applying to the path followed so far. */
979 apr_array_header_t *current;
981 /* Temporary array containing the nodes applying to the next path
982 * segment (used to build up the next contents of CURRENT). */
983 apr_array_header_t *next;
985 /* Scratch pad for path operations. */
986 svn_stringbuf_t *scratch_pad;
988 /* After each lookup iteration, CURRENT and PARENT_RIGHTS will
989 * apply to this path. */
990 svn_stringbuf_t *parent_path;
992 /* Rights that apply at PARENT_PATH, if PARENT_PATH is not empty. */
993 limited_rights_t parent_rights;
997 /* Constructor for lookup_state_t. */
998 static lookup_state_t *
999 create_lookup_state(apr_pool_t *result_pool)
1001 lookup_state_t *state = apr_pcalloc(result_pool, sizeof(*state));
1003 state->next = apr_array_make(result_pool, 4, sizeof(node_t *));
1004 state->current = apr_array_make(result_pool, 4, sizeof(node_t *));
1006 /* Virtually all path segments should fit into this buffer. If they
1007 * don't, the buffer gets automatically reallocated.
1009 * Using a smaller initial size would be fine as well but does not
1010 * buy us much for the increased risk of being expanded anyway - at
1011 * some extra cost. */
1012 state->scratch_pad = svn_stringbuf_create_ensure(200, result_pool);
1014 /* Most paths should fit into this buffer. The same rationale as
1016 state->parent_path = svn_stringbuf_create_ensure(200, result_pool);
1021 /* Clear the current contents of STATE and re-initialize it for ROOT.
1022 * Check whether we can reuse a previous parent path lookup to shorten
1023 * the current PATH walk. Return the full or remaining portion of
1024 * PATH, respectively. PATH must not be NULL. */
1026 init_lockup_state(lookup_state_t *state,
1030 apr_size_t len = strlen(path);
1031 if ( (len > state->parent_path->len)
1032 && state->parent_path->len
1033 && (path[state->parent_path->len] == '/')
1034 && !memcmp(path, state->parent_path->data, state->parent_path->len))
1036 /* The PARENT_PATH of the previous lookup is actually a parent path
1037 * of PATH. The CURRENT node list already matches the parent path
1038 * and we only have to set the correct rights info. */
1039 state->rights = state->parent_rights;
1041 /* Tell the caller where to proceed. */
1042 return path + state->parent_path->len;
1045 /* Start lookup at ROOT for the full PATH. */
1046 state->rights = root->rights;
1047 state->parent_rights = root->rights;
1049 apr_array_clear(state->next);
1050 apr_array_clear(state->current);
1051 APR_ARRAY_PUSH(state->current, node_t *) = root;
1053 /* Var-segment rules match empty segments as well */
1054 if (root->pattern_sub_nodes && root->pattern_sub_nodes->any_var)
1056 node_t *node = root->pattern_sub_nodes->any_var;
1058 /* This is non-recursive due to ACL normalization. */
1059 combine_access(&state->rights, &node->rights);
1060 combine_right_limits(&state->rights, &node->rights);
1061 APR_ARRAY_PUSH(state->current, node_t *) = node;
1064 svn_stringbuf_setempty(state->parent_path);
1065 svn_stringbuf_setempty(state->scratch_pad);
1070 /* Add NODE to the list of NEXT nodes in STATE. NODE may be NULL in which
1071 * case this is a no-op. Also update and aggregate the access rights data
1072 * for the next path segment.
1075 add_next_node(lookup_state_t *state,
1078 /* Allowing NULL nodes simplifies the caller. */
1081 /* The rule with the highest sequence number is the one that applies.
1082 * Not all nodes that we are following have rules that apply directly
1083 * to this path but are mere intermediates that may only have some
1084 * matching deep sub-node. */
1085 combine_access(&state->rights, &node->rights);
1087 /* The rule tree node can be seen as an overlay of all the nodes that
1088 * we are following. Any of them _may_ match eventually, so the min/
1089 * max possible access rights are a combination of all these sub-trees.
1091 combine_right_limits(&state->rights, &node->rights);
1093 /* NODE is now enlisted as a (potential) match for the next segment. */
1094 APR_ARRAY_PUSH(state->next, node_t *) = node;
1096 /* Variable length sub-segment sequences apply to the same node as
1097 * they match empty sequences as well. */
1098 if (node->pattern_sub_nodes && node->pattern_sub_nodes->any_var)
1100 node = node->pattern_sub_nodes->any_var;
1102 /* This is non-recursive due to ACL normalization. */
1103 combine_access(&state->rights, &node->rights);
1104 combine_right_limits(&state->rights, &node->rights);
1105 APR_ARRAY_PUSH(state->next, node_t *) = node;
1110 /* If PREFIX is indeed a prefix (or exact match) or SEGMENT, add the
1111 * node in PREFIX to STATE. */
1113 add_if_prefix_matches(lookup_state_t *state,
1114 const sorted_pattern_t *prefix,
1115 const svn_stringbuf_t *segment)
1117 node_t *node = prefix->node;
1118 if ( node->segment.len <= segment->len
1119 && !memcmp(node->segment.data, segment->data, node->segment.len))
1120 add_next_node(state, node);
1123 /* Scan the PREFIXES array of node_t* for all entries whose SEGMENT members
1124 * are prefixes of SEGMENT. Add these to STATE for the next tree level. */
1126 add_prefix_matches(lookup_state_t *state,
1127 const svn_stringbuf_t *segment,
1128 apr_array_header_t *prefixes)
1130 /* Index of the first node that might be a match. All matches will
1131 * be at this and the immediately following indexes. */
1132 int i = svn_sort__bsearch_lower_bound(prefixes, segment->data,
1133 compare_node_path_segment);
1135 /* The entry we found may be an exact match (but not a true prefix).
1136 * The prefix matching test will still work. */
1137 if (i < prefixes->nelts)
1138 add_if_prefix_matches(state,
1139 &APR_ARRAY_IDX(prefixes, i, sorted_pattern_t),
1142 /* The immediate predecessor may be a true prefix and all potential
1143 * prefixes can be found following the NEXT links between the array
1147 sorted_pattern_t *pattern;
1148 for (pattern = &APR_ARRAY_IDX(prefixes, i - 1, sorted_pattern_t);
1150 pattern = pattern->next)
1152 add_if_prefix_matches(state, pattern, segment);
1157 /* Scan the PATTERNS array of node_t* for all entries whose SEGMENT members
1158 * (usually containing wildcards) match SEGMENT. Add these to STATE for the
1159 * next tree level. */
1161 add_complex_matches(lookup_state_t *state,
1162 const svn_stringbuf_t *segment,
1163 apr_array_header_t *patterns)
1166 for (i = 0; i < patterns->nelts; ++i)
1168 node_t *node = APR_ARRAY_IDX(patterns, i, sorted_pattern_t).node;
1169 if (0 == apr_fnmatch(node->segment.data, segment->data, 0))
1170 add_next_node(state, node);
1174 /* Extract the next segment from PATH and copy it into SEGMENT, whose current
1175 * contents get overwritten. Empty paths ("") are supported and leading '/'
1176 * segment separators will be interpreted as an empty segment (""). Non-
1177 * normalizes parts, i.e. sequences of '/', will be treated as a single '/'.
1179 * Return the start of the next segment within PATH, skipping the '/'
1180 * separator(s). Return NULL, if there are no further segments.
1182 * The caller (only called by lookup(), ATM) must ensure that SEGMENT has
1183 * enough room to store all of PATH.
1186 next_segment(svn_stringbuf_t *segment,
1192 /* Read and scan PATH for NUL and '/' -- whichever comes first. */
1193 for (len = 0, c = *path; c; c = path[++len])
1196 /* End of segment. */
1197 segment->data[len] = 0;
1200 /* If PATH is not normalized, this is where we skip whole sequences
1202 while (path[++len] == '/')
1205 /* Continue behind the last separator in the sequence. We will
1206 * treat trailing '/' as indicating an empty trailing segment.
1207 * Therefore, we never have to return NULL here. */
1212 /* Copy segment contents directly into the result buffer.
1213 * On many architectures, this is almost or entirely for free. */
1214 segment->data[len] = c;
1217 /* No separator found, so all of PATH has been the last segment. */
1218 segment->data[len] = 0;
1221 /* Tell the caller that this has been the last segment. */
1225 /* Starting at the respective user's authz root node provided with STATE,
1226 * follow PATH and return TRUE, iff the REQUIRED access has been granted to
1227 * that user for this PATH. REQUIRED must not contain svn_authz_recursive.
1228 * If RECURSIVE is set, all paths in the sub-tree at and below PATH must
1229 * have REQUIRED access. PATH does not need to be normalized, may be empty
1230 * but must not be NULL.
1232 static svn_boolean_t
1233 lookup(lookup_state_t *state,
1235 authz_access_t required,
1236 svn_boolean_t recursive,
1237 apr_pool_t *scratch_pool)
1239 /* Create a scratch pad large enough to hold any of PATH's segments. */
1240 apr_size_t path_len = strlen(path);
1241 svn_stringbuf_ensure(state->scratch_pad, path_len);
1243 /* Normalize start and end of PATH. Most paths will be fully normalized,
1244 * so keep the overhead as low as possible. */
1245 if (path_len && path[path_len-1] == '/')
1251 while (path_len && path[path_len-1] == '/');
1252 path = apr_pstrmemdup(scratch_pool, path, path_len);
1255 while (path[0] == '/')
1256 ++path; /* Don't update PATH_LEN as we won't need it anymore. */
1258 /* Actually walk the path rule tree following PATH until we run out of
1259 * either tree or PATH. */
1260 while (state->current->nelts && path)
1262 apr_array_header_t *temp;
1264 svn_stringbuf_t *segment = state->scratch_pad;
1266 /* Shortcut 1: We could nowhere find enough rights in this sub-tree. */
1267 if ((state->rights.max_rights & required) != required)
1270 /* Shortcut 2: We will find enough rights everywhere in this sub-tree. */
1271 if ((state->rights.min_rights & required) == required)
1274 /* Extract the next segment. */
1275 path = next_segment(segment, path);
1277 /* Initial state for this segment. */
1278 apr_array_clear(state->next);
1279 state->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
1280 state->rights.access.rights = authz_access_none;
1282 /* These init values ensure that the first node's value will be used
1283 * when combined with them. If there is no first node,
1284 * state->access.sequence_number remains unchanged and we will use
1285 * the parent's (i.e. inherited) access rights. */
1286 state->rights.min_rights = authz_access_write;
1287 state->rights.max_rights = authz_access_none;
1289 /* Update the PARENT_PATH member in STATE to match the nodes in
1290 * CURRENT at the end of this iteration, i.e. if and when NEXT
1291 * has become CURRENT. */
1294 svn_stringbuf_appendbyte(state->parent_path, '/');
1295 svn_stringbuf_appendbytes(state->parent_path, segment->data,
1299 /* Scan follow all alternative routes to the next level. */
1300 for (i = 0; i < state->current->nelts; ++i)
1302 node_t *node = APR_ARRAY_IDX(state->current, i, node_t *);
1303 if (node->sub_nodes)
1304 add_next_node(state, apr_hash_get(node->sub_nodes, segment->data,
1307 /* Process alternative, wildcard-based sub-nodes. */
1308 if (node->pattern_sub_nodes)
1310 add_next_node(state, node->pattern_sub_nodes->any);
1312 /* If the current node represents a "**" pattern, it matches
1313 * to all levels. So, add it to the list for the NEXT level. */
1314 if (node->pattern_sub_nodes->repeat)
1315 add_next_node(state, node);
1317 /* Find all prefix pattern matches. */
1318 if (node->pattern_sub_nodes->prefixes)
1319 add_prefix_matches(state, segment,
1320 node->pattern_sub_nodes->prefixes);
1322 if (node->pattern_sub_nodes->complex)
1323 add_complex_matches(state, segment,
1324 node->pattern_sub_nodes->complex);
1326 /* Find all suffux pattern matches.
1327 * This must be the last check as it destroys SEGMENT. */
1328 if (node->pattern_sub_nodes->suffixes)
1330 /* Suffixes behave like reversed prefixes. */
1331 svn_authz__reverse_string(segment->data, segment->len);
1332 add_prefix_matches(state, segment,
1333 node->pattern_sub_nodes->suffixes);
1338 /* If no rule applied to this SEGMENT directly, the parent rights
1339 * will apply to at least the SEGMENT node itself and possibly
1340 * other parts deeper in it's subtree. */
1341 if (!has_local_rule(&state->rights))
1343 state->rights.access = state->parent_rights.access;
1344 state->rights.min_rights &= state->parent_rights.access.rights;
1345 state->rights.max_rights |= state->parent_rights.access.rights;
1348 /* The list of nodes for SEGMENT is now complete. If we need to
1349 * continue, make it the current and put the old one into the recycler.
1351 * If this is the end of the path, keep the parent path and rights in
1352 * STATE as are such that sibling lookups will benefit from it.
1356 temp = state->current;
1357 state->current = state->next;
1360 /* In STATE, PARENT_PATH, PARENT_RIGHTS and CURRENT are now in sync. */
1361 state->parent_rights = state->rights;
1365 /* If we check recursively, none of the (potential) sub-paths must have
1366 * less than the REQUIRED access rights. "Potential" because we don't
1367 * verify that the respective paths actually exist in the repository.
1370 return (state->rights.min_rights & required) == required;
1372 /* Return whether the access rights on PATH fully include REQUIRED. */
1373 return (state->rights.access.rights & required) == required;
1378 /*** The authz data structure. ***/
1380 /* An entry in svn_authz_t's USER_RULES cache. All members must be
1381 * allocated in the POOL and the latter has to be cleared / destroyed
1382 * before overwriting the entries' contents.
1384 struct authz_user_rules_t
1386 /* User name for which we filtered the rules.
1387 * User NULL for the anonymous user. */
1390 /* Repository name for which we filtered the rules.
1391 * May be empty but never NULL for used entries. */
1392 const char *repository;
1394 /* The combined min/max rights USER has on REPOSITORY. */
1395 authz_rights_t global_rights;
1397 /* Root of the filtered path rule tree.
1398 * Will remain NULL until the first usage. */
1401 /* Reusable lookup state instance. */
1402 lookup_state_t *lookup_state;
1404 /* Pool from which all data within this struct got allocated.
1405 * Can be destroyed or cleaned up with no further side-effects. */
1409 /* Return TRUE, iff AUTHZ matches the pair of REPOS_NAME and USER.
1410 * Note that USER may be NULL.
1412 static svn_boolean_t
1413 matches_filtered_tree(const authz_user_rules_t *authz,
1414 const char *repos_name,
1417 /* Does the user match? */
1420 if (authz->user == NULL || strcmp(user, authz->user))
1423 else if (authz->user != NULL)
1426 /* Does the repository match as well? */
1427 return strcmp(repos_name, authz->repository) == 0;
1430 /* Check if AUTHZ's already contains a path rule tree filtered for this
1431 * USER, REPOS_NAME combination. If that does not exist, yet, create one
1432 * but don't construct the actual filtered tree, yet.
1434 static authz_user_rules_t *
1435 get_user_rules(svn_authz_t *authz,
1436 const char *repos_name,
1441 /* Search our cache for a suitable previously filtered tree. */
1442 if (authz->filtered)
1444 /* Is this a suitable filtered tree? */
1445 if (matches_filtered_tree(authz->filtered, repos_name, user))
1446 return authz->filtered;
1448 /* Drop the old filtered tree before creating a new one. */
1449 svn_pool_destroy(authz->filtered->pool);
1450 authz->filtered = NULL;
1453 /* Global cache lookup. Filter the full model only if necessary. */
1454 pool = svn_pool_create(authz->pool);
1456 /* Write a new entry. */
1457 authz->filtered = apr_palloc(pool, sizeof(*authz->filtered));
1458 authz->filtered->pool = pool;
1459 authz->filtered->repository = apr_pstrdup(pool, repos_name);
1460 authz->filtered->user = user ? apr_pstrdup(pool, user) : NULL;
1461 authz->filtered->lookup_state = create_lookup_state(pool);
1462 authz->filtered->root = NULL;
1464 svn_authz__get_global_rights(&authz->filtered->global_rights,
1465 authz->full, user, repos_name);
1467 return authz->filtered;
1470 /* In AUTHZ's user rules, construct the actual filtered tree.
1471 * Use SCRATCH_POOL for temporary allocations.
1473 static svn_error_t *
1474 filter_tree(svn_authz_t *authz,
1475 apr_pool_t *scratch_pool)
1477 apr_pool_t *pool = authz->filtered->pool;
1478 const char *repos_name = authz->filtered->repository;
1479 const char *user = authz->filtered->user;
1484 svn_membuf_t *key = construct_filtered_key(repos_name, user,
1489 SVN_ERR(svn_object_pool__lookup((void **)&root, filtered_pool, key,
1494 apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
1495 authz_full_t *add_ref = NULL;
1497 /* Make sure the underlying full authz object lives as long as the
1498 * filtered one that we are about to create. We do this by adding
1499 * a reference to it in ITEM_POOL (which may live longer than AUTHZ).
1501 * Note that we already have a reference to that full authz in
1502 * AUTHZ->FULL. Assert that we actually don't created multiple
1503 * instances of the same full model.
1505 svn_error_clear(svn_object_pool__lookup((void **)&add_ref,
1506 authz_pool, authz->authz_id,
1508 SVN_ERR_ASSERT(add_ref == authz->full);
1510 /* Now construct the new filtered tree and cache it. */
1511 root = create_user_authz(authz->full, repos_name, user, item_pool,
1513 svn_error_clear(svn_object_pool__insert((void **)&root,
1514 filtered_pool, key, root,
1520 root = create_user_authz(authz->full, repos_name, user, pool,
1524 /* Write a new entry. */
1525 authz->filtered->root = root;
1527 return SVN_NO_ERROR;
1532 /* Read authz configuration data from PATH into *AUTHZ_P, allocated in
1533 RESULT_POOL. Return the cache key in *AUTHZ_ID. If GROUPS_PATH is set,
1534 use the global groups parsed from it. Use SCRATCH_POOL for temporary
1537 PATH and GROUPS_PATH may be a dirent or an absolute file url. REPOS_HINT
1538 may be specified to speed up access to in-repo authz files.
1540 If PATH or GROUPS_PATH is not a valid authz rule file, then return
1541 SVN_AUTHZ_INVALID_CONFIG. The contents of *AUTHZ_P is then
1542 undefined. If MUST_EXIST is TRUE, a missing authz or global groups file
1543 is also an error. */
1544 static svn_error_t *
1545 authz_read(authz_full_t **authz_p,
1546 svn_membuf_t **authz_id,
1548 const char *groups_path,
1549 svn_boolean_t must_exist,
1550 svn_repos_t *repos_hint,
1551 apr_pool_t *result_pool,
1552 apr_pool_t *scratch_pool)
1554 svn_error_t* err = NULL;
1555 svn_stream_t *rules_stream = NULL;
1556 svn_stream_t *groups_stream = NULL;
1557 svn_checksum_t *rules_checksum = NULL;
1558 svn_checksum_t *groups_checksum = NULL;
1560 config_access_t *config_access =
1561 svn_repos__create_config_access(repos_hint, scratch_pool);
1563 /* Open the main authz file */
1564 SVN_ERR(svn_repos__get_config(&rules_stream, &rules_checksum, config_access,
1565 path, must_exist, scratch_pool));
1567 /* Open the optional groups file */
1569 SVN_ERR(svn_repos__get_config(&groups_stream, &groups_checksum,
1570 config_access, groups_path, must_exist,
1573 /* The authz cache is optional. */
1574 *authz_id = construct_authz_key(rules_checksum, groups_checksum,
1579 SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool,
1580 *authz_id, result_pool));
1582 /* If not found, parse and add to cache. */
1585 apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
1587 /* Parse the configuration(s) and construct the full authz model
1589 err = svn_authz__parse(authz_p, rules_stream, groups_stream,
1590 item_pool, scratch_pool);
1591 if (err != SVN_NO_ERROR)
1593 /* That pool would otherwise never get destroyed. */
1594 svn_pool_destroy(item_pool);
1596 /* Add the URL / file name to the error stack since the parser
1597 * doesn't have it. */
1598 err = svn_error_quick_wrapf(err,
1599 "Error while parsing config file: '%s':",
1604 SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool,
1605 *authz_id, *authz_p,
1606 item_pool, result_pool));
1612 /* Parse the configuration(s) and construct the full authz model from
1614 err = svn_error_quick_wrapf(svn_authz__parse(authz_p, rules_stream,
1616 result_pool, scratch_pool),
1617 "Error while parsing authz file: '%s':",
1621 svn_repos__destroy_config_access(config_access);
1628 /*** Public functions. ***/
1631 svn_repos_authz_read3(svn_authz_t **authz_p,
1633 const char *groups_path,
1634 svn_boolean_t must_exist,
1635 svn_repos_t *repos_hint,
1636 apr_pool_t *result_pool,
1637 apr_pool_t *scratch_pool)
1639 svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
1640 authz->pool = result_pool;
1642 SVN_ERR(authz_read(&authz->full, &authz->authz_id, path, groups_path,
1643 must_exist, repos_hint, result_pool, scratch_pool));
1646 return SVN_NO_ERROR;
1651 svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
1652 svn_stream_t *groups_stream, apr_pool_t *pool)
1654 apr_pool_t *scratch_pool = svn_pool_create(pool);
1655 svn_authz_t *authz = apr_pcalloc(pool, sizeof(*authz));
1658 /* Parse the configuration and construct the full authz model from it. */
1659 SVN_ERR(svn_authz__parse(&authz->full, stream, groups_stream, pool,
1662 svn_pool_destroy(scratch_pool);
1665 return SVN_NO_ERROR;
1669 svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
1670 const char *path, const char *user,
1671 svn_repos_authz_access_t required_access,
1672 svn_boolean_t *access_granted,
1675 const authz_access_t required =
1676 ((required_access & svn_authz_read ? authz_access_read_flag : 0)
1677 | (required_access & svn_authz_write ? authz_access_write_flag : 0));
1679 /* Pick or create the suitable pre-filtered path rule tree. */
1680 authz_user_rules_t *rules = get_user_rules(
1682 (repos_name ? repos_name : AUTHZ_ANY_REPOSITORY),
1685 /* In many scenarios, users have uniform access to a repository
1686 * (blanket access or no access at all).
1688 * In these cases, don't bother creating or consulting the filtered tree.
1690 if ((rules->global_rights.min_access & required) == required)
1692 *access_granted = TRUE;
1693 return SVN_NO_ERROR;
1696 if ((rules->global_rights.max_access & required) != required)
1698 *access_granted = FALSE;
1699 return SVN_NO_ERROR;
1702 /* No specific path given, i.e. looking for anywhere in the tree? */
1706 ((rules->global_rights.max_access & required) == required);
1707 return SVN_NO_ERROR;
1710 /* Rules tree lookup */
1712 /* Did we already filter the data model? */
1714 SVN_ERR(filter_tree(authz, pool));
1716 /* Re-use previous lookup results, if possible. */
1717 path = init_lockup_state(authz->filtered->lookup_state,
1718 authz->filtered->root, path);
1721 SVN_ERR_ASSERT(path[0] == '/');
1723 /* Determine the granted access for the requested path.
1724 * PATH does not need to be normalized for lockup(). */
1725 *access_granted = lookup(rules->lookup_state, path, required,
1726 !!(required_access & svn_authz_recursive), pool);
1728 return SVN_NO_ERROR;