--- apache-couchdb-1.0.1.orig/src/couchdb/couch_httpd_auth.erl	2010-06-23 22:21:30.000000000 -0700
+++ apache-couchdb-1.0.1/src/couchdb/couch_httpd_auth.erl	2010-11-26 19:33:30.000000000 -0800
@@ -17,6 +17,7 @@
 -export([cookie_authentication_handler/1]).
 -export([null_authentication_handler/1]).
 -export([proxy_authentification_handler/1]).
+-export([webproxy_authentication_handler/1]).
 -export([cookie_auth_header/2]).
 -export([handle_session_req/1]).
 
@@ -347,3 +348,133 @@
 make_cookie_time() ->
     {NowMS, NowS, _} = erlang:now(),
     NowMS * 1000000 + NowS.
+
+%%
+%% webproxy auth handler %%
+%%
+%% This handler allows a user authentication by an external system.
+%% It expects the external system passes 'Authorization Basic' or 'Authorization Digest' header.
+%% The authenticated username and corresponding user roles will be set into the userCtx object.
+%% Corresponding user roles will be referred from the /$authentication_db/org.couchdb.user:$username document.
+%%
+%% The following article suggested to use the null_authentication_handler, but it doesn't maintain userCtx object.
+%% ->  http://wiki.apache.org/couchdb/Apache_As_a_Reverse_Proxy 
+%%
+%% This handler uses new config entry, require_authentication_db_entry, the possible value is true or false.
+%%   If it's true, then the authentication_db document should be existing for each authenticated user.
+%%   It's the default behavior.
+%%
+%%   If it's false and there is no corresponding $username document at $authentication_db, 
+%%   then the $username and empty role will be set into the userCxt object.
+%%
+%% Security consideration:
+%%   If an user connects to couchdb's port directly, such as curl http://127.0.0.1:5984/, 
+%%   with a dummy header, like 'Authorization: Digest username="admin"', then the user will get the admin user's priviledge.
+%%
+%%   There are config entries, webproxy_use_secret and webproxy_secret_value, as an option for the above issue.
+%%     If the webproxy_use_secret is true, then the X-Auth-CouchDB-Token request header is expected.
+%%     The value is a static and should be same as the result of couch_util:to_hex(crypto:sha_mac(Secret, webproxy_secret_value)).
+%%     The Secret is the secret key in couch_httpd_auth section of ini.
+%%     The default value of webproxy_secret_value is Secret.
+%% 
+webproxy_authentication_handler(Req) ->
+    XHeaderToken = couch_config:get("couch_httpd_auth", "x_auth_token", "X-Auth-CouchDB-Token"),
+    case couch_config:get("couch_httpd_auth", "webproxy_use_secret", "false") of
+	"true" ->
+	    case couch_config:get("couch_httpd_auth", "secret", nil) of
+		nil -> 
+		    throw({unauthorized, <<"scret should be defined on couch_httpd_auth.">>});
+		Secret ->
+		    case couch_config:get("couch_httpd_auth", "webproxy_secret_value", nil) of
+			nil ->
+			    ExpectedToken = couch_util:to_hex(crypto:sha_mac(Secret, Secret));
+			Seed ->
+			    ExpectedToken = couch_util:to_hex(crypto:sha_mac(Secret, Seed))
+		    end,
+		    case header_value(Req, XHeaderToken) of
+			Token when Token == ExpectedToken ->
+			    webproxy_authentication_handler_main(Req);
+			_ -> 
+			    throw({unauthorized, <<"unmatch token header.">>})
+		    end
+	    end;
+	_ -> webproxy_authentication_handler_main(Req)
+    end.
+
+webproxy_authentication_handler_main(Req) ->
+    AuthorizationHeader = header_value(Req, "Authorization"),
+    case AuthorizationHeader of
+	"Basic " ++ _ -> 
+	    webproxy_basic_auth(Req);
+	"Digest " ++ DigestValue ->
+	    webproxy_digest_auth(Req, DigestValue);
+	_ -> 
+	    webproxy_default_terminate_action(Req)
+    end.
+
+webproxy_digest_find_user([H|T]) ->
+    case H of
+	["username",U] -> 
+	    %% RFC2069 says U must be a quoted-string, so remove double quote charaters.
+	    User = string:sub_string(U,2,string:len(U)-1),
+	    ["username",User];
+	_ -> webproxy_digest_find_user(T)
+    end.
+
+webproxy_default_terminate_action(Req) ->
+    %% reference: http://wiki.apache.org/couchdb/Security_Features_Overview
+    case couch_server:has_admins() of
+        true ->
+            Req;
+        false ->
+            case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of
+                "true" -> Req;
+		%% If no admins, and no user required, then everyone is admin!
+		%% Yay, admin party!
+                _ -> Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}}
+            end
+    end.
+
+webproxy_basic_auth(Req) ->
+    case basic_name_pw(Req) of
+	{User, _} ->
+	    case couch_auth_cache:get_user_creds(User) of
+		nil ->
+		    case couch_config:get("couch_httpd_auth", "require_authentication_db_entry", "true") of
+			"true" -> 
+			    throw({unauthorized, <<"Name couldn't be found on authentication_db.">>});
+			_ ->
+			    Req#httpd{user_ctx=#user_ctx{name=?l2b(User), roles=[]}}
+		    end;
+		UserProps ->
+		    Req#httpd{user_ctx=#user_ctx{
+				name=?l2b(User),
+				roles=couch_util:get_value(<<"roles">>, UserProps, [])
+			       }}
+	    end;
+	_ -> webproxy_default_terminate_action(Req)
+    end.
+
+webproxy_digest_auth(Req, DigestValue) ->
+    %% DigestValue might be "username=\"yasu\", realm=\"CouchDB\", ..."
+    DigestKVSplitFun = fun(X) -> string:tokens(string:strip(X), "=") end, 
+    DigestItemList = [DigestKVSplitFun(X) || X <- string:tokens(DigestValue,",")],
+    %% DigestItemList might be [[key0,value0], ["username","yasu"], [key1,value1], ...]
+    case webproxy_digest_find_user(DigestItemList) of
+	["username", User] -> 
+            case couch_auth_cache:get_user_creds(User) of
+		nil ->
+		    case couch_config:get("couch_httpd_auth", "require_authentication_db_entry", "true") of
+			"true" -> 
+			    throw({unauthorized, <<"Name couldn't be found on authentication_db.">>});
+			_ ->
+			    Req#httpd{user_ctx=#user_ctx{name=?l2b(User), roles=[]}}
+		    end;
+		UserProps -> 
+		    Req#httpd{user_ctx=#user_ctx{
+				name=?l2b(User),
+				roles=couch_util:get_value(<<"roles">>, UserProps, [])
+			       }}
+            end;
+	_ -> webproxy_default_terminate_action(Req)
+    end.
