Fix OAuth authentication with VHosts + URL rewriting The OAuth handler was not getting the right path (the one the client used to compute its OAuth signature) to verify the client's signature. The right path is the one from before doing the VHost dispatch. Secondly, after the OAuth handler succeeds, the rewriter kicks in and calls couch_httpd:handle_request_int/5 with a new mochiweb request which contains the rewritten patch. This will cause all the authentication handlers to run again, which makes the OAuth handler fail this second time because it gets a rewritten patch.
COUCHDB-1320 Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/094cfe79 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/094cfe79 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/094cfe79 Branch: refs/heads/master Commit: 094cfe795286b74303ec517c2262e4845428def9 Parents: 8b94951 Author: Filipe David Borba Manana <[email protected]> Authored: Fri Nov 25 20:12:36 2011 +0000 Committer: Filipe David Borba Manana <[email protected]> Committed: Sat Dec 10 19:40:26 2011 +0000 ---------------------------------------------------------------------- src/couchdb/couch_httpd.erl | 3 +- src/couchdb/couch_httpd_oauth.erl | 11 +++- src/couchdb/couch_httpd_rewrite.erl | 4 +- test/etap/160-vhosts.t | 89 +++++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/094cfe79/src/couchdb/couch_httpd.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index d668f98..97475c5 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -299,7 +299,8 @@ handle_request_int(MochiReq, DefaultFun, db_url_handlers = DbUrlHandlers, design_url_handlers = DesignUrlHandlers, default_fun = DefaultFun, - url_handlers = UrlHandlers + url_handlers = UrlHandlers, + user_ctx = erlang:erase(pre_rewrite_user_ctx) }, HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun), http://git-wip-us.apache.org/repos/asf/couchdb/blob/094cfe79/src/couchdb/couch_httpd_oauth.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_httpd_oauth.erl b/src/couchdb/couch_httpd_oauth.erl index 4d58a88..65304a3 100644 --- a/src/couchdb/couch_httpd_oauth.erl +++ b/src/couchdb/couch_httpd_oauth.erl @@ -133,8 +133,15 @@ serve_oauth(#httpd{mochi_req=MochiReq}=Req, Fun, FailSilently) -> % get requested path RequestedPath = case MochiReq:get_header_value("x-couchdb-requested-path") of - undefined -> MochiReq:get(raw_path); - RequestedPath0 -> RequestedPath0 + undefined -> + case MochiReq:get_header_value("x-couchdb-vhost-path") of + undefined -> + MochiReq:get(raw_path); + VHostPath -> + VHostPath + end; + RequestedPath0 -> + RequestedPath0 end, {_, QueryString, _} = mochiweb_util:urlsplit_path(RequestedPath), http://git-wip-us.apache.org/repos/asf/couchdb/blob/094cfe79/src/couchdb/couch_httpd_rewrite.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index bf93478..c8cab85 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -187,8 +187,10 @@ handle_rewrite_req(#httpd{ db_url_handlers = DbUrlHandlers, design_url_handlers = DesignUrlHandlers, default_fun = DefaultFun, - url_handlers = UrlHandlers + url_handlers = UrlHandlers, + user_ctx = UserCtx } = Req, + erlang:put(pre_rewrite_user_ctx, UserCtx), couch_httpd:handle_request_int(MochiReq1, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers) end. http://git-wip-us.apache.org/repos/asf/couchdb/blob/094cfe79/test/etap/160-vhosts.t ---------------------------------------------------------------------- diff --git a/test/etap/160-vhosts.t b/test/etap/160-vhosts.t index 6e26b59..94882fe 100755 --- a/test/etap/160-vhosts.t +++ b/test/etap/160-vhosts.t @@ -30,7 +30,7 @@ admin_user_ctx() -> {user_ctx, #user_ctx{roles=[<<"_admin">>]}}. main(_) -> test_util:init_code_path(), - etap:plan(17), + etap:plan(20), case (catch test()) of ok -> etap:end_tests(); @@ -113,9 +113,11 @@ test() -> test_vhost_request_path2(), test_vhost_request_path3(), test_vhost_request_to_root(), + test_vhost_request_with_oauth(Db), %% restart boilerplate couch_db:close(Db), + ok = couch_server:delete(couch_db:name(Db), [admin_user_ctx()]), timer:sleep(3000), couch_server_sup:stop(), @@ -282,3 +284,88 @@ test_vhost_request_to_root() -> etap:is(HasCouchDBWelcome, true, "should allow redirect to /"); _Else -> etap:is(false, true, <<"ibrowse fail">>) end. + +test_vhost_request_with_oauth(Db) -> + {ok, AuthDb} = couch_db:create( + <<"tap_test_sec_db">>, [admin_user_ctx(), overwrite]), + PrevAuthDbName = couch_config:get("couch_httpd_auth", "authentication_db"), + couch_config:set("couch_httpd_auth", "authentication_db", "tap_test_sec_db", false), + couch_config:set("oauth_token_users", "otoksec1", "joe", false), + couch_config:set("oauth_consumer_secrets", "consec1", "foo", false), + couch_config:set("oauth_token_secrets", "otoksec1", "foobar", false), + couch_config:set("couch_httpd_auth", "require_valid_user", "true", false), + + DDoc = couch_doc:from_json_obj({[ + {<<"_id">>, <<"_design/test">>}, + {<<"language">>, <<"javascript">>}, + {<<"rewrites">>, [ + {[ + {<<"from">>, <<"foobar">>}, + {<<"to">>, <<"_info">>} + ]} + ]} + ]}), + {ok, _} = couch_db:update_doc(Db, DDoc, []), + + RewritePath = "/etap-test-db/_design/test/_rewrite/foobar", + ok = couch_config:set("vhosts", "oauth-example.com", RewritePath, false), + couch_httpd_vhost:reload(), + + case ibrowse:send_req(server(), [], get, [], [{host_header, "oauth-example.com"}]) of + {ok, "401", _, Body} -> + {JsonBody} = ejson:decode(Body), + etap:is( + couch_util:get_value(<<"error">>, JsonBody), + <<"unauthorized">>, + "Request without OAuth credentials failed"); + Error -> + etap:bail("Request without OAuth credentials did not fail: " ++ + couch_util:to_list(Error)) + end, + + JoeDoc = couch_doc:from_json_obj({[ + {<<"_id">>, <<"org.couchdb.user:joe">>}, + {<<"type">>, <<"user">>}, + {<<"name">>, <<"joe">>}, + {<<"roles">>, []}, + {<<"password_sha">>, <<"fe95df1ca59a9b567bdca5cbaf8412abd6e06121">>}, + {<<"salt">>, <<"4e170ffeb6f34daecfd814dfb4001a73">>} + ]}), + {ok, _} = couch_db:update_doc(AuthDb, JoeDoc, []), + + Url = "http://oauth-example.com/", + Consumer = {"consec1", "foo", hmac_sha1}, + SignedParams = oauth:signed_params( + "GET", Url, [], Consumer, "otoksec1", "foobar"), + OAuthUrl = oauth:uri(server(), SignedParams), + + case ibrowse:send_req(OAuthUrl, [], get, [], [{host_header, "oauth-example.com"}]) of + {ok, "200", _, Body2} -> + {JsonBody2} = ejson:decode(Body2), + etap:is(couch_util:get_value(<<"name">>, JsonBody2), <<"test">>, + "should return ddoc info with OAuth credentials"); + Error2 -> + etap:bail("Failed to access vhost with OAuth credentials: " ++ + couch_util:to_list(Error2)) + end, + + Consumer2 = {"consec1", "bad_secret", hmac_sha1}, + SignedParams2 = oauth:signed_params( + "GET", Url, [], Consumer2, "otoksec1", "foobar"), + OAuthUrl2 = oauth:uri(server(), SignedParams2), + + case ibrowse:send_req(OAuthUrl2, [], get, [], [{host_header, "oauth-example.com"}]) of + {ok, "401", _, Body3} -> + {JsonBody3} = ejson:decode(Body3), + etap:is( + couch_util:get_value(<<"error">>, JsonBody3), + <<"unauthorized">>, + "Request with bad OAuth credentials failed"); + Error3 -> + etap:bail("Failed to access vhost with bad OAuth credentials: " ++ + couch_util:to_list(Error3)) + end, + + couch_config:set("couch_httpd_auth", "authentication_db", PrevAuthDbName, false), + couch_config:set("couch_httpd_auth", "require_valid_user", "false", false), + ok = couch_server:delete(couch_db:name(AuthDb), [admin_user_ctx()]).
