Browse files

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
  • Loading branch information...
1 parent 8b94951 commit 094cfe795286b74303ec517c2262e4845428def9 @fdmanana fdmanana committed Nov 25, 2011
Showing with 102 additions and 5 deletions.
  1. +2 −1 src/couchdb/couch_httpd.erl
  2. +9 −2 src/couchdb/couch_httpd_oauth.erl
  3. +3 −1 src/couchdb/couch_httpd_rewrite.erl
  4. +88 −1 test/etap/160-vhosts.t
View
3 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),
View
11 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),
View
4 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.
View
89 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()]).

0 comments on commit 094cfe7

Please sign in to comment.