From 8e2215ee6306b0f4c13553796d401e9f5f93bcb6 Mon Sep 17 00:00:00 2001 From: "Damien F. Katz" Date: Tue, 4 Aug 2009 19:50:46 +0000 Subject: Initial check-in of OAuth and cookie authentication. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@800938 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd.erl | 134 +++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 83 deletions(-) (limited to 'src/couchdb/couch_httpd.erl') 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). -- cgit v1.2.3