Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / mod_authz_svn / mod_authz_svn.c
blobd658f10c9a574ee13970940916c2e5c70f8c0bc2
1 /*
2 * mod_authz_svn.c: an Apache mod_dav_svn sub-module to provide path
3 * based authorization for a Subversion repository.
5 * ====================================================================
6 * Copyright (c) 2003-2008 CollabNet. All rights reserved.
8 * This software is licensed as described in the file COPYING, which
9 * you should have received as part of this distribution. The terms
10 * are also available at http://subversion.tigris.org/license-1.html.
11 * If newer versions of this license are posted there, you may use a
12 * newer version instead, at your option.
14 * This software consists of voluntary contributions made by many
15 * individuals. For exact contribution history, see the revision
16 * history and logs, available at http://subversion.tigris.org/.
17 * ====================================================================
22 #include <httpd.h>
23 #include <http_config.h>
24 #include <http_core.h>
25 #include <http_request.h>
26 #include <http_protocol.h>
27 #include <http_log.h>
28 #include <ap_config.h>
29 #include <ap_provider.h>
30 #include <apr_uri.h>
31 #include <apr_lib.h>
32 #include <mod_dav.h>
34 #include "mod_dav_svn.h"
35 #include "mod_authz_svn.h"
36 #include "svn_path.h"
37 #include "svn_config.h"
38 #include "svn_string.h"
39 #include "svn_repos.h"
42 extern module AP_MODULE_DECLARE_DATA authz_svn_module;
44 typedef struct {
45 int authoritative;
46 int anonymous;
47 int no_auth_when_anon_ok;
48 const char *base_path;
49 const char *access_file;
50 const char *force_username_case;
51 } authz_svn_config_rec;
54 * Configuration
57 static void *
58 create_authz_svn_dir_config(apr_pool_t *p, char *d)
60 authz_svn_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
61 conf->base_path = d;
63 /* By default keep the fortress secure */
64 conf->authoritative = 1;
65 conf->anonymous = 1;
67 return conf;
70 static const command_rec authz_svn_cmds[] =
72 AP_INIT_FLAG("AuthzSVNAuthoritative", ap_set_flag_slot,
73 (void *)APR_OFFSETOF(authz_svn_config_rec, authoritative),
74 OR_AUTHCFG,
75 "Set to 'Off' to allow access control to be passed along to "
76 "lower modules. (default is On.)"),
77 AP_INIT_TAKE1("AuthzSVNAccessFile", ap_set_file_slot,
78 (void *)APR_OFFSETOF(authz_svn_config_rec, access_file),
79 OR_AUTHCFG,
80 "Text file containing permissions of repository paths."),
81 AP_INIT_FLAG("AuthzSVNAnonymous", ap_set_flag_slot,
82 (void *)APR_OFFSETOF(authz_svn_config_rec, anonymous),
83 OR_AUTHCFG,
84 "Set to 'Off' to disable two special-case behaviours of "
85 "this module: (1) interaction with the 'Satisfy Any' "
86 "directive, and (2) enforcement of the authorization "
87 "policy even when no 'Require' directives are present. "
88 "(default is On.)"),
89 AP_INIT_FLAG("AuthzSVNNoAuthWhenAnonymousAllowed", ap_set_flag_slot,
90 (void *)APR_OFFSETOF(authz_svn_config_rec,
91 no_auth_when_anon_ok),
92 OR_AUTHCFG,
93 "Set to 'On' to suppress authentication and authorization "
94 "for requests which anonymous users are allowed to perform. "
95 "(default is Off.)"),
96 AP_INIT_TAKE1("AuthzForceUsernameCase", ap_set_string_slot,
97 (void *)APR_OFFSETOF(authz_svn_config_rec,
98 force_username_case),
99 OR_AUTHCFG,
100 "Set to 'Upper' or 'Lower' to convert the username before "
101 "checking for authorization."),
102 { NULL }
106 * Get the, possibly cached, svn_authz_t for this request.
108 static svn_authz_t *
109 get_access_conf(request_rec *r, authz_svn_config_rec *conf)
111 const char *cache_key = NULL;
112 void *user_data = NULL;
113 svn_authz_t *access_conf = NULL;
114 svn_error_t *svn_err;
115 char errbuf[256];
117 cache_key = apr_pstrcat(r->pool, "mod_authz_svn:",
118 conf->access_file, NULL);
119 apr_pool_userdata_get(&user_data, cache_key, r->connection->pool);
120 access_conf = user_data;
121 if (access_conf == NULL)
123 svn_err = svn_repos_authz_read(&access_conf, conf->access_file,
124 TRUE, r->connection->pool);
125 if (svn_err)
127 ap_log_rerror(APLOG_MARK, APLOG_ERR,
128 /* If it is an error code that APR can make sense
129 of, then show it, otherwise, pass zero to avoid
130 putting "APR does not understand this error code"
131 in the error log. */
132 ((svn_err->apr_err >= APR_OS_START_USERERR &&
133 svn_err->apr_err < APR_OS_START_CANONERR) ?
134 0 : svn_err->apr_err),
135 r, "Failed to load the AuthzSVNAccessFile: %s",
136 svn_err_best_message(svn_err, errbuf, sizeof(errbuf)));
137 svn_error_clear(svn_err);
138 access_conf = NULL;
140 else
142 /* Cache the open repos for the next request on this connection */
143 apr_pool_userdata_set(access_conf, cache_key,
144 NULL, r->connection->pool);
147 return access_conf;
150 /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
151 converts it to lower case. */
152 static void
153 convert_case(char *text, svn_boolean_t to_uppercase)
155 char *c = text;
156 while (*c)
158 *c = (to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
159 ++c;
163 /* Return the username to authorize, with case-conversion performed if
164 CONF->force_username_case is set. */
165 static char *
166 get_username_to_authorize(request_rec *r, authz_svn_config_rec *conf)
168 char *username_to_authorize = r->user;
169 if (conf->force_username_case)
171 username_to_authorize = apr_pstrdup(r->pool, r->user);
172 convert_case(username_to_authorize,
173 strcasecmp(conf->force_username_case, "upper") == 0);
175 return username_to_authorize;
178 /* Check if the current request R is allowed. Upon exit *REPOS_PATH_REF
179 * will contain the path and repository name that an operation was requested
180 * on in the form 'name:path'. *DEST_REPOS_PATH_REF will contain the
181 * destination path if the requested operation was a MOVE or a COPY.
182 * Returns OK when access is allowed, DECLINED when it isn't, or an HTTP_
183 * error code when an error occurred.
185 static int
186 req_check_access(request_rec *r,
187 authz_svn_config_rec *conf,
188 const char **repos_path_ref,
189 const char **dest_repos_path_ref)
191 const char *dest_uri;
192 apr_uri_t parsed_dest_uri;
193 const char *cleaned_uri;
194 int trailing_slash;
195 const char *repos_name;
196 const char *dest_repos_name;
197 const char *relative_path;
198 const char *repos_path;
199 const char *dest_repos_path = NULL;
200 dav_error *dav_err;
201 svn_repos_authz_access_t authz_svn_type = svn_authz_none;
202 svn_boolean_t authz_access_granted = FALSE;
203 svn_authz_t *access_conf = NULL;
204 svn_error_t *svn_err;
205 char errbuf[256];
206 const char *username_to_authorize = get_username_to_authorize(r, conf);
208 switch (r->method_number)
210 /* All methods requiring read access to all subtrees of r->uri */
211 case M_COPY:
212 authz_svn_type |= svn_authz_recursive;
214 /* All methods requiring read access to r->uri */
215 case M_OPTIONS:
216 case M_GET:
217 case M_PROPFIND:
218 case M_REPORT:
219 authz_svn_type |= svn_authz_read;
220 break;
222 /* All methods requiring write access to all subtrees of r->uri */
223 case M_MOVE:
224 case M_DELETE:
225 authz_svn_type |= svn_authz_recursive;
227 /* All methods requiring write access to r->uri */
228 case M_MKCOL:
229 case M_PUT:
230 case M_PROPPATCH:
231 case M_CHECKOUT:
232 case M_MERGE:
233 case M_MKACTIVITY:
234 case M_LOCK:
235 case M_UNLOCK:
236 authz_svn_type |= svn_authz_write;
237 break;
239 default:
240 /* Require most strict access for unknown methods */
241 authz_svn_type |= svn_authz_write | svn_authz_recursive;
242 break;
245 dav_err = dav_svn_split_uri(r,
246 r->uri,
247 conf->base_path,
248 &cleaned_uri,
249 &trailing_slash,
250 &repos_name,
251 &relative_path,
252 &repos_path);
253 if (dav_err)
255 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
256 "%s [%d, #%d]",
257 dav_err->desc, dav_err->status, dav_err->error_id);
258 /* Ensure that we never allow access by dav_err->status */
259 return (dav_err->status != OK && dav_err->status != DECLINED) ?
260 dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
263 /* Ignore the URI passed to MERGE, like mod_dav_svn does.
264 * See issue #1821.
265 * XXX: When we start accepting a broader range of DeltaV MERGE
266 * XXX: requests, this should be revisited.
268 if (r->method_number == M_MERGE)
269 repos_path = NULL;
271 if (repos_path)
272 repos_path = svn_path_join("/", repos_path, r->pool);
274 *repos_path_ref = apr_pstrcat(r->pool, repos_name, ":", repos_path, NULL);
276 if (r->method_number == M_MOVE || r->method_number == M_COPY)
278 dest_uri = apr_table_get(r->headers_in, "Destination");
280 /* Decline MOVE or COPY when there is no Destination uri, this will
281 * cause failure.
283 if (!dest_uri)
284 return DECLINED;
286 apr_uri_parse(r->pool, dest_uri, &parsed_dest_uri);
288 ap_unescape_url(parsed_dest_uri.path);
289 dest_uri = parsed_dest_uri.path;
290 if (strncmp(dest_uri, conf->base_path, strlen(conf->base_path)))
292 /* If it is not the same location, then we don't allow it.
293 * XXX: Instead we could compare repository uuids, but that
294 * XXX: seems a bit over the top.
296 return HTTP_BAD_REQUEST;
299 dav_err = dav_svn_split_uri(r,
300 dest_uri,
301 conf->base_path,
302 &cleaned_uri,
303 &trailing_slash,
304 &dest_repos_name,
305 &relative_path,
306 &dest_repos_path);
308 if (dav_err)
310 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
311 "%s [%d, #%d]",
312 dav_err->desc, dav_err->status, dav_err->error_id);
313 /* Ensure that we never allow access by dav_err->status */
314 return (dav_err->status != OK && dav_err->status != DECLINED) ?
315 dav_err->status : HTTP_INTERNAL_SERVER_ERROR;
318 if (dest_repos_path)
319 dest_repos_path = svn_path_join("/", dest_repos_path, r->pool);
321 *dest_repos_path_ref = apr_pstrcat(r->pool, dest_repos_name, ":",
322 dest_repos_path, NULL);
325 /* Retrieve/cache authorization file */
326 access_conf = get_access_conf(r,conf);
327 if (access_conf == NULL)
328 return DECLINED;
330 /* Perform authz access control.
332 * First test the special case where repos_path == NULL, and skip
333 * calling the authz routines in that case. This is an oddity of
334 * the DAV RA method: some requests have no repos_path, but apache
335 * still triggers an authz lookup for the URI.
337 * However, if repos_path == NULL and the request requires write
338 * access, then perform a global authz lookup. The request is
339 * denied if the user commiting isn't granted any access anywhere
340 * in the repository. This is to avoid operations that involve no
341 * paths (commiting an empty revision, leaving a dangling
342 * transaction in the FS) being granted by default, letting
343 * unauthenticated users write some changes to the repository.
344 * This was issue #2388.
346 * XXX: For now, requesting access to the entire repository always
347 * XXX: succeeds, until we come up with a good way of figuring
348 * XXX: this out.
350 if (repos_path
351 || (!repos_path && (authz_svn_type & svn_authz_write)))
353 svn_err = svn_repos_authz_check_access(access_conf, repos_name,
354 repos_path,
355 username_to_authorize,
356 authz_svn_type,
357 &authz_access_granted,
358 r->pool);
359 if (svn_err)
361 ap_log_rerror(APLOG_MARK, APLOG_ERR,
362 /* If it is an error code that APR can make
363 sense of, then show it, otherwise, pass
364 zero to avoid putting "APR does not
365 understand this error code" in the error
366 log. */
367 ((svn_err->apr_err >= APR_OS_START_USERERR &&
368 svn_err->apr_err < APR_OS_START_CANONERR) ?
369 0 : svn_err->apr_err),
370 r, "Failed to perform access control: %s",
371 svn_err_best_message(svn_err, errbuf,
372 sizeof(errbuf)));
373 svn_error_clear(svn_err);
375 return DECLINED;
377 if (!authz_access_granted)
378 return DECLINED;
381 /* XXX: MKCOL, MOVE, DELETE
382 * XXX: Require write access to the parent dir of repos_path.
385 /* XXX: PUT
386 * XXX: If the path doesn't exist, require write access to the
387 * XXX: parent dir of repos_path.
390 /* Only MOVE and COPY have a second uri we have to check access to. */
391 if (r->method_number != M_MOVE && r->method_number != M_COPY)
392 return OK;
394 /* Check access on the destination repos_path. Again, skip this if
395 repos_path == NULL (see above for explanations) */
396 if (repos_path)
398 svn_err = svn_repos_authz_check_access(access_conf,
399 dest_repos_name,
400 dest_repos_path,
401 username_to_authorize,
402 svn_authz_write
403 |svn_authz_recursive,
404 &authz_access_granted,
405 r->pool);
406 if (svn_err)
408 ap_log_rerror(APLOG_MARK, APLOG_ERR,
409 /* If it is an error code that APR can make sense
410 of, then show it, otherwise, pass zero to avoid
411 putting "APR does not understand this error code"
412 in the error log. */
413 ((svn_err->apr_err >= APR_OS_START_USERERR &&
414 svn_err->apr_err < APR_OS_START_CANONERR) ?
415 0 : svn_err->apr_err),
416 r, "Failed to perform access control: %s",
417 svn_err_best_message(svn_err, errbuf, sizeof(errbuf)));
418 svn_error_clear(svn_err);
420 return DECLINED;
422 if (!authz_access_granted)
423 return DECLINED;
426 /* XXX: MOVE and COPY, if the path doesn't exist yet, also
427 * XXX: require write access to the parent dir of dest_repos_path.
430 return OK;
433 /* Log a message indicating the access control decision made about a
434 * request. FILE and LINE should be supplied via the APLOG_MARK macro.
435 * ALLOWED is boolean. REPOS_PATH and DEST_REPOS_PATH are information
436 * about the request. DEST_REPOS_PATH may be NULL. */
437 static void
438 log_access_verdict(const char *file, int line,
439 const request_rec *r, int allowed,
440 const char *repos_path, const char *dest_repos_path)
442 int level = allowed ? APLOG_INFO : APLOG_ERR;
443 const char *verdict = allowed ? "granted" : "denied";
445 if (r->user)
447 if (dest_repos_path)
448 ap_log_rerror(file, line, level, 0, r,
449 "Access %s: '%s' %s %s %s", verdict, r->user,
450 r->method, repos_path, dest_repos_path);
451 else
452 ap_log_rerror(file, line, level, 0, r,
453 "Access %s: '%s' %s %s", verdict, r->user,
454 r->method, repos_path);
456 else
458 if (dest_repos_path)
459 ap_log_rerror(file, line, level, 0, r,
460 "Access %s: - %s %s %s", verdict,
461 r->method, repos_path, dest_repos_path);
462 else
463 ap_log_rerror(file, line, level, 0, r,
464 "Access %s: - %s %s", verdict,
465 r->method, repos_path);
470 * This function is used as a provider to allow mod_dav_svn to bypass the
471 * generation of an apache request when checking GET access from
472 * "mod_dav_svn/authz.c" .
474 static int
475 subreq_bypass(request_rec *r,
476 const char *repos_path,
477 const char *repos_name)
479 svn_error_t *svn_err = NULL;
480 svn_authz_t *access_conf = NULL;
481 authz_svn_config_rec *conf = NULL;
482 svn_boolean_t authz_access_granted = FALSE;
483 char errbuf[256];
484 const char *username_to_authorize;
486 conf = ap_get_module_config(r->per_dir_config,
487 &authz_svn_module);
488 username_to_authorize = get_username_to_authorize(r, conf);
490 /* If configured properly, this should never be true, but just in case. */
491 if (!conf->anonymous || !conf->access_file)
493 log_access_verdict(APLOG_MARK, r, 0, repos_path, NULL);
494 return HTTP_FORBIDDEN;
497 /* Retrieve authorization file */
498 access_conf = get_access_conf(r,conf);
499 if (access_conf == NULL)
500 return HTTP_FORBIDDEN;
502 /* Perform authz access control.
503 * See similarly labeled comment in req_check_access.
505 if (repos_path)
507 svn_err = svn_repos_authz_check_access(access_conf, repos_name,
508 repos_path,
509 username_to_authorize,
510 svn_authz_none|svn_authz_read,
511 &authz_access_granted,
512 r->pool);
513 if (svn_err)
515 ap_log_rerror(APLOG_MARK, APLOG_ERR,
516 /* If it is an error code that APR can make
517 sense of, then show it, otherwise, pass
518 zero to avoid putting "APR does not
519 understand this error code" in the error
520 log. */
521 ((svn_err->apr_err >= APR_OS_START_USERERR &&
522 svn_err->apr_err < APR_OS_START_CANONERR) ?
523 0 : svn_err->apr_err),
524 r, "Failed to perform access control: %s",
525 svn_err_best_message(svn_err, errbuf, sizeof(errbuf)));
526 svn_error_clear(svn_err);
527 return HTTP_FORBIDDEN;
529 if (!authz_access_granted)
531 log_access_verdict(APLOG_MARK, r, 0, repos_path, NULL);
532 return HTTP_FORBIDDEN;
536 log_access_verdict(APLOG_MARK, r, 1, repos_path, NULL);
538 return OK;
542 * Hooks
545 static int
546 access_checker(request_rec *r)
548 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
549 &authz_svn_module);
550 const char *repos_path;
551 const char *dest_repos_path = NULL;
552 int status;
554 /* We are not configured to run */
555 if (!conf->anonymous || !conf->access_file)
556 return DECLINED;
558 if (ap_some_auth_required(r))
560 /* It makes no sense to check if a location is both accessible
561 * anonymous and by an authenticated user (in the same request!).
563 if (ap_satisfies(r) != SATISFY_ANY)
564 return DECLINED;
566 /* If the user is trying to authenticate, let him. If anonymous
567 * access is allowed, so is authenticated access, by definition
568 * of the meaning of '*' in the access file.
570 if (apr_table_get(r->headers_in,
571 (PROXYREQ_PROXY == r->proxyreq)
572 ? "Proxy-Authorization" : "Authorization"))
574 /* Given Satisfy Any is in effect, we have to forbid access
575 * to let the auth_checker hook have a go at it.
577 return HTTP_FORBIDDEN;
581 /* If anon access is allowed, return OK */
582 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
583 if (status == DECLINED)
585 if (!conf->authoritative)
586 return DECLINED;
588 if (!ap_some_auth_required(r))
589 log_access_verdict(APLOG_MARK, r, 0, repos_path, dest_repos_path);
591 return HTTP_FORBIDDEN;
594 if (status != OK)
595 return status;
597 log_access_verdict(APLOG_MARK, r, 1, repos_path, dest_repos_path);
599 return OK;
602 static int
603 check_user_id(request_rec *r)
605 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
606 &authz_svn_module);
607 const char *repos_path;
608 const char *dest_repos_path = NULL;
609 int status;
611 /* We are not configured to run, or, an earlier module has already
612 * authenticated this request. */
613 if (!conf->access_file || !conf->no_auth_when_anon_ok || r->user)
614 return DECLINED;
616 /* If anon access is allowed, return OK, preventing later modules
617 * from issuing an HTTP_UNAUTHORIZED. Also pass a note to our
618 * auth_checker hook that access has already been checked. */
619 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
620 if (status == OK)
622 apr_table_setn(r->notes, "authz_svn-anon-ok", (const char*)1);
623 log_access_verdict(APLOG_MARK, r, 1, repos_path, dest_repos_path);
624 return OK;
627 return status;
630 static int
631 auth_checker(request_rec *r)
633 authz_svn_config_rec *conf = ap_get_module_config(r->per_dir_config,
634 &authz_svn_module);
635 const char *repos_path;
636 const char *dest_repos_path = NULL;
637 int status;
639 /* We are not configured to run */
640 if (!conf->access_file)
641 return DECLINED;
643 /* Previous hook (check_user_id) already did all the work,
644 * and, as a sanity check, r->user hasn't been set since then? */
645 if (!r->user && apr_table_get(r->notes, "authz_svn-anon-ok"))
646 return OK;
648 status = req_check_access(r, conf, &repos_path, &dest_repos_path);
649 if (status == DECLINED)
651 if (conf->authoritative)
653 log_access_verdict(APLOG_MARK, r, 0, repos_path, dest_repos_path);
654 ap_note_auth_failure(r);
655 return HTTP_FORBIDDEN;
657 return DECLINED;
660 if (status != OK)
661 return status;
663 log_access_verdict(APLOG_MARK, r, 1, repos_path, dest_repos_path);
665 return OK;
669 * Module flesh
672 static void
673 register_hooks(apr_pool_t *p)
675 static const char * const mod_ssl[] = { "mod_ssl.c", NULL };
677 ap_hook_access_checker(access_checker, NULL, NULL, APR_HOOK_LAST);
678 /* Our check_user_id hook must be before any module which will return
679 * HTTP_UNAUTHORIZED (mod_auth_basic, etc.), but after mod_ssl, to
680 * give SSLOptions +FakeBasicAuth a chance to work. */
681 ap_hook_check_user_id(check_user_id, mod_ssl, NULL, APR_HOOK_FIRST);
682 ap_hook_auth_checker(auth_checker, NULL, NULL, APR_HOOK_FIRST);
683 ap_register_provider(p,
684 AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP,
685 AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME,
686 AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER,
687 (void*)subreq_bypass);
690 module AP_MODULE_DECLARE_DATA authz_svn_module =
692 STANDARD20_MODULE_STUFF,
693 create_authz_svn_dir_config, /* dir config creater */
694 NULL, /* dir merger --- default is to override */
695 NULL, /* server config */
696 NULL, /* merge server config */
697 authz_svn_cmds, /* command apr_table_t */
698 register_hooks /* register hooks */