summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_httpd.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb/couch_httpd.erl')
-rw-r--r--src/couchdb/couch_httpd.erl134
1 files changed, 51 insertions, 83 deletions
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index 616ada8c..766d18d0 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -23,8 +23,6 @@
-export([start_json_response/2, start_json_response/3, end_json_response/1]).
-export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
-export([send_json/2,send_json/3,send_json/4]).
--export([default_authentication_handler/1,special_test_authentication_handler/1]).
--export([null_authentication_handler/1]).
start_link() ->
% read config and register for configuration changes
@@ -111,6 +109,9 @@ make_arity_2_fun(SpecStr) ->
fun(Arg1, Arg2) -> apply(Mod, Fun, [Arg1, Arg2]) end
end.
+% SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}"
+make_arity_1_fun_list(SpecStr) ->
+ [make_arity_1_fun(FunSpecStr) || FunSpecStr <- re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}])].
stop() ->
mochiweb_http:stop(?MODULE).
@@ -119,8 +120,8 @@ stop() ->
handle_request(MochiReq, DefaultFun,
UrlHandlers, DbUrlHandlers, DesignUrlHandlers) ->
Begin = now(),
- AuthenticationFun = make_arity_1_fun(
- couch_config:get("httpd", "authentication_handler")),
+ AuthenticationFuns = make_arity_1_fun_list(
+ couch_config:get("httpd", "authentication_handlers")),
% for the path, use the raw path with the query string and fragment
% removed, but URL quoting left intact
RawUri = MochiReq:get(raw_path),
@@ -171,11 +172,25 @@ handle_request(MochiReq, DefaultFun,
{ok, Resp} =
try
- HandlerFun(HttpReq#httpd{user_ctx=AuthenticationFun(HttpReq)})
+ % Try authentication handlers in order until one returns a result
+ case lists:foldl(fun(_Fun, #httpd{user_ctx=#user_ctx{}}=Req) -> Req;
+ (Fun, #httpd{}=Req) -> Fun(Req);
+ (_Fun, Response) -> Response
+ end, HttpReq, AuthenticationFuns) of
+ #httpd{user_ctx=#user_ctx{}}=Req -> HandlerFun(Req);
+ #httpd{}=Req ->
+ case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of
+ "true" ->
+ throw({unauthorized, <<"Authentication required.">>});
+ _ ->
+ HandlerFun(Req#httpd{user_ctx=#user_ctx{}})
+ end;
+ Response -> Response
+ end
catch
throw:Error ->
- % ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]),
- % ?LOG_DEBUG("Stacktrace: ~p",[erlang:get_stacktrace()]),
+ ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]),
+ ?LOG_DEBUG("Stacktrace: ~p",[erlang:get_stacktrace()]),
send_error(HttpReq, Error);
error:badarg ->
?LOG_ERROR("Badarg error in HTTP request",[]),
@@ -205,48 +220,6 @@ handle_request(MochiReq, DefaultFun,
increment_method_stats(Method) ->
couch_stats_collector:increment({httpd_request_methods, Method}).
-special_test_authentication_handler(Req) ->
- case header_value(Req, "WWW-Authenticate") of
- "X-Couch-Test-Auth " ++ NamePass ->
- % NamePass is a colon separated string: "joe schmoe:a password".
- {ok, [Name, Pass]} = regexp:split(NamePass, ":"),
- case {Name, Pass} of
- {"Jan Lehnardt", "apple"} -> ok;
- {"Christopher Lenz", "dog food"} -> ok;
- {"Noah Slater", "biggiesmalls endian"} -> ok;
- {"Chris Anderson", "mp3"} -> ok;
- {"Damien Katz", "pecan pie"} -> ok;
- {_, _} ->
- throw({unauthorized, <<"Name or password is incorrect.">>})
- end,
- #user_ctx{name=?l2b(Name)};
- _ ->
- % No X-Couch-Test-Auth credentials sent, give admin access so the
- % previous authentication can be restored after the test
- #user_ctx{roles=[<<"_admin">>]}
- end.
-
-default_authentication_handler(Req) ->
- case basic_username_pw(Req) of
- {User, Pass} ->
- case couch_server:is_admin(User, Pass) of
- true ->
- #user_ctx{name=?l2b(User), roles=[<<"_admin">>]};
- false ->
- throw({unauthorized, <<"Name or password is incorrect.">>})
- end;
- nil ->
- case couch_server:has_admins() of
- true ->
- #user_ctx{};
- false ->
- % if no admins, then everyone is admin! Yay, admin party!
- #user_ctx{roles=[<<"_admin">>]}
- end
- end.
-
-null_authentication_handler(_Req) ->
- #user_ctx{roles=[<<"_admin">>]}.
% Utilities
@@ -265,8 +238,9 @@ header_value(#httpd{mochi_req=MochiReq}, Key, Default) ->
primary_header_value(#httpd{mochi_req=MochiReq}, Key) ->
MochiReq:get_primary_header_value(Key).
-serve_file(#httpd{mochi_req=MochiReq}, RelativePath, DocumentRoot) ->
- {ok, MochiReq:serve_file(RelativePath, DocumentRoot, server_header())}.
+serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot) ->
+ {ok, MochiReq:serve_file(RelativePath, DocumentRoot,
+ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, []))}.
qs_value(Req, Key) ->
qs_value(Req, Key, undefined).
@@ -307,11 +281,16 @@ recv_chunked(#httpd{mochi_req=MochiReq}, MaxChunkSize, ChunkFun, InitState) ->
% called with Length == 0 on the last time.
MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState).
-body(#httpd{mochi_req=MochiReq}) ->
- % Maximum size of document PUT request body (4GB)
- MaxSize = list_to_integer(
- couch_config:get("couchdb", "max_document_size", "4294967296")),
- MochiReq:recv_body(MaxSize).
+body(#httpd{mochi_req=MochiReq, req_body=ReqBody}) ->
+ case ReqBody of
+ undefined ->
+ % Maximum size of document PUT request body (4GB)
+ MaxSize = list_to_integer(
+ couch_config:get("couchdb", "max_document_size", "4294967296")),
+ MochiReq:recv_body(MaxSize);
+ _Else ->
+ ReqBody
+ end.
json_body(Httpd) ->
?JSON_DECODE(body(Httpd)).
@@ -357,37 +336,21 @@ verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) ->
-basic_username_pw(Req) ->
- case header_value(Req, "Authorization") of
- "Basic " ++ Base64Value ->
- case string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),":") of
- [User, Pass] ->
- {User, Pass};
- [User] ->
- {User, ""};
- _ ->
- nil
- end;
- _ ->
- nil
- end.
-
-
-start_chunked_response(#httpd{mochi_req=MochiReq}, Code, Headers) ->
+start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
couch_stats_collector:increment({httpd_status_codes, Code}),
- {ok, MochiReq:respond({Code, Headers ++ server_header(), chunked})}.
+ {ok, MochiReq:respond({Code, Headers ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers), chunked})}.
send_chunk(Resp, Data) ->
Resp:write_chunk(Data),
{ok, Resp}.
-send_response(#httpd{mochi_req=MochiReq}, Code, Headers, Body) ->
+send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) ->
couch_stats_collector:increment({httpd_status_codes, Code}),
if Code >= 400 ->
?LOG_DEBUG("httpd ~p error response:~n ~s", [Code, Body]);
true -> ok
end,
- {ok, MochiReq:respond({Code, Headers ++ server_header(), Body})}.
+ {ok, MochiReq:respond({Code, Headers ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers), Body})}.
send_method_not_allowed(Req, Methods) ->
send_response(Req, 405, [{"Allow", Methods}], <<>>).
@@ -508,17 +471,22 @@ error_info(Error) ->
send_error(_Req, {already_sent, Resp, _Error}) ->
{ok, Resp};
-send_error(Req, Error) ->
+send_error(#httpd{mochi_req=MochiReq}=Req, Error) ->
{Code, ErrorStr, ReasonStr} = error_info(Error),
- if Code == 401 ->
- case couch_config:get("httpd", "WWW-Authenticate", nil) of
- nil ->
- Headers = [];
+ Headers = if Code == 401 ->
+ case MochiReq:get_header_value("X-CouchDB-WWW-Authenticate") of
+ undefined ->
+ case couch_config:get("httpd", "WWW-Authenticate", nil) of
+ nil ->
+ [];
+ Type ->
+ [{"WWW-Authenticate", Type}]
+ end;
Type ->
- Headers = [{"WWW-Authenticate", Type}]
+ [{"WWW-Authenticate", Type}]
end;
true ->
- Headers = []
+ []
end,
send_error(Req, Code, Headers, ErrorStr, ReasonStr).