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/b86fa1f6 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/b86fa1f6 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/b86fa1f6 Branch: refs/heads/1.2.x Commit: b86fa1f6bedee9d441bf4cac53c2794a60c69216 Parents: 25754ac Author: Filipe David Borba Manana <[email protected]> Authored: Sat Dec 10 19:05:52 2011 +0000 Committer: Filipe David Borba Manana <[email protected]> Committed: Sat Dec 10 19:40:37 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/b86fa1f6/src/couchdb/couch_httpd.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 11b0bca..2d4c38d 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -298,7 +298,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/b86fa1f6/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/b86fa1f6/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/b86fa1f6/test/etap/160-vhosts.t ---------------------------------------------------------------------- diff --git a/test/etap/160-vhosts.t b/test/etap/160-vhosts.t index e959f74..0b239a1 100755 --- a/test/etap/160-vhosts.t +++ b/test/etap/160-vhosts.t @@ -52,7 +52,7 @@ admin_user_ctx() -> {user_ctx, #user_ctx{roles=[<<"_admin">>]}}. main(_) -> test_util:init_code_path(), - etap:plan(15), + etap:plan(18), case (catch test()) of ok -> etap:end_tests(); @@ -135,9 +135,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(), @@ -301,3 +303,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()]).
