Allow OAuth credentials to be stored in user documents If the ini configuration parameter `use_users_db` (section `couch_httpd_oauth`) is set to true, OAuth credentials can be stored in user documents (system database _users) instead. The credentials are stored in a top level propery of user documents named `oauth`. Example:
{ "_id": "org.couchdb.user:joe", "type": "user", "name": "joe", "password_sha": "fe95df1ca59a9b567bdca5cbaf8412abd6e06121", "salt": "4e170ffeb6f34daecfd814dfb4001a73" "roles": ["foo", "bar"], "oauth": { "consumer_keys": { "consumerKey1": "key1Secret", "consumerKey2": "key2Secret" }, "tokens": { "token1": "token1Secret", "token2": "token2Secret" } } } Closes COUCHDB-1238. Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/d01faab3 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/d01faab3 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/d01faab3 Branch: refs/heads/COUCHDB-1342 Commit: d01faab3f464ff0806f4ad9f4166ca7a498a4866 Parents: 75b6e09 Author: Filipe David Borba Manana <fdman...@apache.org> Authored: Wed Jan 4 15:51:00 2012 +0000 Committer: Filipe David Borba Manana <fdman...@apache.org> Committed: Wed Jan 4 15:51:00 2012 +0000 ---------------------------------------------------------------------- etc/couchdb/default.ini.tpl.in | 24 ++ share/Makefile.am | 1 + share/www/script/couch_tests.js | 1 + share/www/script/test/oauth.js | 4 +- share/www/script/test/oauth_users_db.js | 161 +++++++++++ src/couchdb/couch_httpd_oauth.erl | 371 ++++++++++++++++++++------ src/couchdb/couch_js_functions.hrl | 18 ++ 7 files changed, 491 insertions(+), 89 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/etc/couchdb/default.ini.tpl.in ---------------------------------------------------------------------- diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in index 4bc485e..ef6bf97 100644 --- a/etc/couchdb/default.ini.tpl.in +++ b/etc/couchdb/default.ini.tpl.in @@ -65,6 +65,30 @@ require_valid_user = false timeout = 600 ; number of seconds before automatic logout auth_cache_size = 50 ; size is number of cache entries +[couch_httpd_oauth] +; If set to 'true', oauth token and consumer secrets will be looked up +; in the authentication database (_users). These secrets are stored in +; a top level property named "oauth" in user documents. Example: +; { +; "_id": "org.couchdb.user:joe", +; "type": "user", +; "name": "joe", +; "password_sha": "fe95df1ca59a9b567bdca5cbaf8412abd6e06121", +; "salt": "4e170ffeb6f34daecfd814dfb4001a73" +; "roles": ["foo", "bar"], +; "oauth": { +; "consumer_keys": { +; "consumerKey1": "key1Secret", +; "consumerKey2": "key2Secret" +; }, +; "tokens": { +; "token1": "token1Secret", +; "token2": "token2Secret" +; } +; } +; } +use_users_db = false + [query_servers] javascript = %bindir%/%couchjs_command_name% %localbuilddatadir%/server/main.js coffeescript = %bindir%/%couchjs_command_name% %localbuilddatadir%/server/main-coffee.js http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/share/Makefile.am ---------------------------------------------------------------------- diff --git a/share/Makefile.am b/share/Makefile.am index 2ef679a..aa73b6b 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -164,6 +164,7 @@ nobase_dist_localdata_DATA = \ www/script/test/method_override.js \ www/script/test/multiple_rows.js \ www/script/test/oauth.js \ + www/script/test/oauth_users_db.js \ www/script/test/proxyauth.js \ www/script/test/purge.js \ www/script/test/reader_acl.js \ http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/share/www/script/couch_tests.js ---------------------------------------------------------------------- diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index a6d4b56..c1cdf75 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -58,6 +58,7 @@ loadTest("multiple_rows.js"); loadScript("script/oauth.js"); loadScript("script/sha1.js"); loadTest("oauth.js"); +loadTest("oauth_users_db.js"); loadTest("proxyauth.js"); loadTest("purge.js"); loadTest("reader_acl.js"); http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/share/www/script/test/oauth.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js index 6398f94..6bc4773 100644 --- a/share/www/script/test/oauth.js +++ b/share/www/script/test/oauth.js @@ -283,7 +283,9 @@ couchTests.oauth = function(debug) { {section: "oauth_token_secrets", key: "bar", value: admintokenSecret}, {section: "couch_httpd_oauth", - key: "authorization_url", value: authorization_url} + key: "authorization_url", value: authorization_url}, + {section: "couch_httpd_oauth", + key: "use_users_db", value: "false"} ], testFun ); http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/share/www/script/test/oauth_users_db.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/oauth_users_db.js b/share/www/script/test/oauth_users_db.js new file mode 100644 index 0000000..b98069e --- /dev/null +++ b/share/www/script/test/oauth_users_db.js @@ -0,0 +1,161 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.oauth_users_db = function(debug) { + // This tests OAuth authentication using the _users DB instead of the ini + // configuration for storing OAuth tokens and secrets. + + if (debug) debugger; + + var usersDb = new CouchDB("test_suite_users",{"X-Couch-Full-Commit":"false"}); + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + var host = CouchDB.host; + var authorization_url = "/_oauth/authorize"; + + + // Simple secret key generator + function generateSecret(length) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var secret = ''; + for (var i = 0; i < length; i++) { + secret += tab.charAt(Math.floor(Math.random() * 64)); + } + return secret; + } + + + function oauthRequest(method, path, message, accessor) { + message.action = path; + message.method = method || 'GET'; + OAuth.SignatureMethod.sign(message, accessor); + var parameters = message.parameters; + if (method == "POST" || method == "GET") { + if (method == "GET") { + return CouchDB.request("GET", OAuth.addToURL(path, parameters)); + } else { + return CouchDB.request("POST", path, { + headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: OAuth.formEncode(parameters) + }); + } + } else { + return CouchDB.request(method, path, { + headers: {Authorization: OAuth.getAuthorizationHeader('', parameters)} + }); + } + } + + + // this function will be called on the modified server + var testFun = function () { + var fdmanana = CouchDB.prepareUserDoc({ + name: "fdmanana", + roles: ["dev"], + oauth: { + consumer_keys: { + "key_foo": "bar", + "key_xpto": "mars" + }, + tokens: { + "salut": "ola", + "tok1": "123" + } + } + }, "qwerty"); + TEquals(true, usersDb.save(fdmanana).ok); + + var signatureMethods = ["PLAINTEXT", "HMAC-SHA1"]; + var message, xhr, responseMessage, accessor, data; + + for (var i = 0; i < signatureMethods.length; i++) { + message = { + parameters: { + oauth_signature_method: signatureMethods[i], + oauth_consumer_key: "key_foo", + oauth_token: "tok1", + oauth_version: "1.0" + } + }; + accessor = { + consumerSecret: "bar", + tokenSecret: "123" + }; + + xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", + message, accessor + ); + TEquals(200, xhr.status); + + responseMessage = OAuth.decodeForm(xhr.responseText); + + // Obtaining User Authorization + // Only needed for 3-legged OAuth + //xhr = CouchDB.request( + // "GET", authorization_url + '?oauth_token=' + responseMessage.oauth_token); + //TEquals(200, xhr.status); + + xhr = oauthRequest( + "GET", CouchDB.protocol + host + "/_session", message, accessor); + TEquals(200, xhr.status); + data = JSON.parse(xhr.responseText); + TEquals(true, data.ok); + TEquals("object", typeof data.userCtx); + TEquals("fdmanana", data.userCtx.name); + TEquals("dev", data.userCtx.roles[0]); + TEquals("oauth", data.info.authenticated); + + // test invalid token + message.parameters.oauth_token = "not a token!"; + xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session", + message, accessor + ); + TEquals(400, xhr.status, "Request should be invalid."); + + // test invalid secret + message.parameters.oauth_token = "tok1"; + accessor.tokenSecret = "badone"; + xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session", + message, accessor + ); + data = JSON.parse(xhr.responseText); + TEquals(null, data.userCtx.name); + TEquals(1, data.userCtx.roles.length); + TEquals("_admin", data.userCtx.roles[0]); + TEquals(true, data.info.authentication_handlers.indexOf("default") >= 0); + TEquals("default", data.info.authenticated); + } + }; + + + usersDb.deleteDb(); + + run_on_modified_server( + [ + {section: "httpd", + key: "WWW-Authenticate", value: 'OAuth'}, + {section: "couch_httpd_auth", + key: "secret", value: generateSecret(64)}, + {section: "couch_httpd_auth", + key: "authentication_db", value: usersDb.name}, + {section: "couch_httpd_oauth", + key: "use_users_db", value: "true"}, + {section: "httpd", key: "authentication_handlers", + value: "{couch_httpd_oauth, oauth_authentication_handler}, " + + "{couch_httpd_auth, default_authentication_handler}"} + ], + testFun + ); + + // cleanup + usersDb.deleteDb(); + db.deleteDb(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/src/couchdb/couch_httpd_oauth.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_httpd_oauth.erl b/src/couchdb/couch_httpd_oauth.erl index 65304a3..90990e6 100644 --- a/src/couchdb/couch_httpd_oauth.erl +++ b/src/couchdb/couch_httpd_oauth.erl @@ -11,69 +11,116 @@ % the License. -module(couch_httpd_oauth). + -include("couch_db.hrl"). +-include("couch_js_functions.hrl"). + +-export([oauth_authentication_handler/1, handle_oauth_req/1]). + +-define(OAUTH_DDOC_ID, <<"_design/oauth">>). +-define(OAUTH_VIEW_NAME, <<"oauth_credentials">>). --export([oauth_authentication_handler/1, handle_oauth_req/1, consumer_lookup/2]). +-record(callback_params, { + consumer, + token, + token_secret, + url, + signature, + params, + username +}). % OAuth auth handler using per-node user db -oauth_authentication_handler(#httpd{mochi_req=MochiReq}=Req) -> - serve_oauth(Req, fun(URL, Params, Consumer, Signature) -> - AccessToken = couch_util:get_value("oauth_token", Params), - case couch_config:get("oauth_token_secrets", AccessToken) of - undefined -> - couch_httpd:send_error(Req, 400, <<"invalid_token">>, - <<"Invalid OAuth token.">>); - TokenSecret -> - ?LOG_DEBUG("OAuth URL is: ~p", [URL]), - case oauth:verify(Signature, atom_to_list(MochiReq:get(method)), URL, Params, Consumer, TokenSecret) of - true -> - set_user_ctx(Req, AccessToken); - false -> - Req - end - end - end, true). +oauth_authentication_handler(Req) -> + serve_oauth(Req, fun oauth_auth_callback/2, true). + + +oauth_auth_callback(Req, #callback_params{token_secret = undefined}) -> + couch_httpd:send_error( + Req, 400, <<"invalid_token">>, <<"Invalid OAuth token.">>); + +oauth_auth_callback(#httpd{mochi_req = MochiReq} = Req, CbParams) -> + Method = atom_to_list(MochiReq:get(method)), + #callback_params{ + consumer = Consumer, + token_secret = TokenSecret, + url = Url, + signature = Sig, + params = Params, + username = User + } = CbParams, + case oauth:verify(Sig, Method, Url, Params, Consumer, TokenSecret) of + true -> + set_user_ctx(Req, User); + false -> + ?LOG_DEBUG("OAuth handler: signature verification failed for user `~p`~n" + "Received signature is `~p`~n" + "HTTP method is `~p`~n" + "URL is `~p`~n" + "Parameters are `~p`~n" + "Consumer is `~p`, token secret is `~p`~n" + "Expected signature was `~p`~n", + [User, Sig, Method, Url, Params, Consumer, TokenSecret, + oauth:signature(Method, Url, Params, Consumer, TokenSecret)]), + Req + end. + % Look up the consumer key and get the roles to give the consumer -set_user_ctx(Req, AccessToken) -> - % TODO move to db storage - Name = case couch_config:get("oauth_token_users", AccessToken) of - undefined -> throw({bad_request, unknown_oauth_token}); - Value -> ?l2b(Value) - end, +set_user_ctx(_Req, undefined) -> + throw({bad_request, unknown_oauth_token}); +set_user_ctx(Req, Name) -> case couch_auth_cache:get_user_creds(Name) of - nil -> Req; + nil -> + ?LOG_DEBUG("OAuth handler: user `~p` credentials not found", [Name]), + Req; User -> Roles = couch_util:get_value(<<"roles">>, User, []), Req#httpd{user_ctx=#user_ctx{name=Name, roles=Roles}} end. % OAuth request_token -handle_oauth_req(#httpd{path_parts=[_OAuth, <<"request_token">>], method=Method}=Req) -> - serve_oauth(Req, fun(URL, Params, Consumer, Signature) -> - AccessToken = couch_util:get_value("oauth_token", Params), - TokenSecret = couch_config:get("oauth_token_secrets", AccessToken), - case oauth:verify(Signature, atom_to_list(Method), URL, Params, Consumer, TokenSecret) of - true -> - ok(Req, <<"oauth_token=requestkey&oauth_token_secret=requestsecret">>); - false -> - invalid_signature(Req) +handle_oauth_req(#httpd{path_parts=[_OAuth, <<"request_token">>], method=Method}=Req1) -> + serve_oauth(Req1, fun(Req, CbParams) -> + #callback_params{ + consumer = Consumer, + token_secret = TokenSecret, + url = Url, + signature = Sig, + params = Params + } = CbParams, + case oauth:verify( + Sig, atom_to_list(Method), Url, Params, Consumer, TokenSecret) of + true -> + ok(Req, <<"oauth_token=requestkey&oauth_token_secret=requestsecret">>); + false -> + invalid_signature(Req) end end, false); handle_oauth_req(#httpd{path_parts=[_OAuth, <<"authorize">>]}=Req) -> {ok, serve_oauth_authorize(Req)}; -handle_oauth_req(#httpd{path_parts=[_OAuth, <<"access_token">>], method='GET'}=Req) -> - serve_oauth(Req, fun(URL, Params, Consumer, Signature) -> - case oauth:token(Params) of - "requestkey" -> - case oauth:verify(Signature, "GET", URL, Params, Consumer, "requestsecret") of - true -> - ok(Req, <<"oauth_token=accesskey&oauth_token_secret=accesssecret">>); - false -> - invalid_signature(Req) - end; - _ -> - couch_httpd:send_error(Req, 400, <<"invalid_token">>, <<"Invalid OAuth token.">>) +handle_oauth_req(#httpd{path_parts=[_OAuth, <<"access_token">>], method='GET'}=Req1) -> + serve_oauth(Req1, fun(Req, CbParams) -> + #callback_params{ + consumer = Consumer, + token = Token, + url = Url, + signature = Sig, + params = Params + } = CbParams, + case Token of + "requestkey" -> + case oauth:verify( + Sig, "GET", Url, Params, Consumer, "requestsecret") of + true -> + ok(Req, + <<"oauth_token=accesskey&oauth_token_secret=accesssecret">>); + false -> + invalid_signature(Req) + end; + _ -> + couch_httpd:send_error( + Req, 400, <<"invalid_token">>, <<"Invalid OAuth token.">>) end end, false); handle_oauth_req(#httpd{path_parts=[_OAuth, <<"access_token">>]}=Req) -> @@ -83,35 +130,49 @@ invalid_signature(Req) -> couch_httpd:send_error(Req, 400, <<"invalid_signature">>, <<"Invalid signature value.">>). % This needs to be protected i.e. force user to login using HTTP Basic Auth or form-based login. -serve_oauth_authorize(#httpd{method=Method}=Req) -> +serve_oauth_authorize(#httpd{method=Method}=Req1) -> case Method of 'GET' -> % Confirm with the User that they want to authenticate the Consumer - serve_oauth(Req, fun(URL, Params, Consumer, Signature) -> - AccessToken = couch_util:get_value("oauth_token", Params), - TokenSecret = couch_config:get("oauth_token_secrets", AccessToken), - case oauth:verify(Signature, "GET", URL, Params, Consumer, TokenSecret) of - true -> - ok(Req, <<"oauth_token=requestkey&oauth_token_secret=requestsecret">>); - false -> - invalid_signature(Req) + serve_oauth(Req1, fun(Req, CbParams) -> + #callback_params{ + consumer = Consumer, + token_secret = TokenSecret, + url = Url, + signature = Sig, + params = Params + } = CbParams, + case oauth:verify( + Sig, "GET", Url, Params, Consumer, TokenSecret) of + true -> + ok(Req, <<"oauth_token=requestkey&", + "oauth_token_secret=requestsecret">>); + false -> + invalid_signature(Req) end end, false); 'POST' -> % If the User has confirmed, we direct the User back to the Consumer with a verification code - serve_oauth(Req, fun(URL, Params, Consumer, Signature) -> - AccessToken = couch_util:get_value("oauth_token", Params), - TokenSecret = couch_config:get("oauth_token_secrets", AccessToken), - case oauth:verify(Signature, "POST", URL, Params, Consumer, TokenSecret) of - true -> - %redirect(oauth_callback, oauth_token, oauth_verifier), - ok(Req, <<"oauth_token=requestkey&oauth_token_secret=requestsecret">>); - false -> - invalid_signature(Req) + serve_oauth(Req1, fun(Req, CbParams) -> + #callback_params{ + consumer = Consumer, + token_secret = TokenSecret, + url = Url, + signature = Sig, + params = Params + } = CbParams, + case oauth:verify( + Sig, "POST", Url, Params, Consumer, TokenSecret) of + true -> + %redirect(oauth_callback, oauth_token, oauth_verifier), + ok(Req, <<"oauth_token=requestkey&", + "oauth_token_secret=requestsecret">>); + false -> + invalid_signature(Req) end end, false); _ -> - couch_httpd:send_method_not_allowed(Req, "GET,POST") + couch_httpd:send_method_not_allowed(Req1, "GET,POST") end. serve_oauth(#httpd{mochi_req=MochiReq}=Req, Fun, FailSilently) -> @@ -157,36 +218,170 @@ serve_oauth(#httpd{mochi_req=MochiReq}=Req, Fun, FailSilently) -> false -> couch_httpd:send_error(Req, 400, <<"invalid_consumer">>, <<"Invalid consumer.">>) end; ConsumerKey -> - SigMethod = couch_util:get_value("oauth_signature_method", Params), - case consumer_lookup(ConsumerKey, SigMethod) of - none -> - couch_httpd:send_error(Req, 400, <<"invalid_consumer">>, <<"Invalid consumer (key or signature method).">>); - Consumer -> - Signature = couch_util:get_value("oauth_signature", Params), - URL = couch_httpd:absolute_uri(Req, RequestedPath), - Fun(URL, proplists:delete("oauth_signature", Params), - Consumer, Signature) + Url = couch_httpd:absolute_uri(Req, RequestedPath), + case get_callback_params(ConsumerKey, Params, Url) of + {ok, CallbackParams} -> + Fun(Req, CallbackParams); + invalid_consumer_token_pair -> + couch_httpd:send_error( + Req, 400, + <<"invalid_consumer_token_pair">>, + <<"Invalid consumer and token pair.">>); + {error, {Error, Reason}} -> + couch_httpd:send_error(Req, 400, Error, Reason) end end; _ -> couch_httpd:send_error(Req, 400, <<"invalid_oauth_version">>, <<"Invalid OAuth version.">>) end. -consumer_lookup(Key, MethodStr) -> - SignatureMethod = case MethodStr of - "PLAINTEXT" -> plaintext; - "HMAC-SHA1" -> hmac_sha1; - %"RSA-SHA1" -> rsa_sha1; - _Else -> undefined - end, - case SignatureMethod of - undefined -> none; - _SupportedMethod -> - case couch_config:get("oauth_consumer_secrets", Key, undefined) of - undefined -> none; - Secret -> {Key, Secret, SignatureMethod} - end + +get_callback_params(ConsumerKey, Params, Url) -> + Token = couch_util:get_value("oauth_token", Params), + SigMethod = sig_method(Params), + CbParams0 = #callback_params{ + token = Token, + signature = couch_util:get_value("oauth_signature", Params), + params = proplists:delete("oauth_signature", Params), + url = Url + }, + case oauth_credentials_info(Token, ConsumerKey) of + nil -> + invalid_consumer_token_pair; + {error, _} = Err -> + Err; + {OauthCreds} -> + User = couch_util:get_value(<<"username">>, OauthCreds, []), + ConsumerSecret = ?b2l(couch_util:get_value( + <<"consumer_secret">>, OauthCreds, <<>>)), + TokenSecret = ?b2l(couch_util:get_value( + <<"token_secret">>, OauthCreds, <<>>)), + case (User =:= []) orelse (ConsumerSecret =:= []) orelse + (TokenSecret =:= []) of + true -> + invalid_consumer_token_pair; + false -> + CbParams = CbParams0#callback_params{ + consumer = {ConsumerKey, ConsumerSecret, SigMethod}, + token_secret = TokenSecret, + username = User + }, + ?LOG_DEBUG("Got OAuth credentials, for ConsumerKey `~p` and " + "Token `~p`, from the views, User: `~p`, " + "ConsumerSecret: `~p`, TokenSecret: `~p`", + [ConsumerKey, Token, User, ConsumerSecret, TokenSecret]), + {ok, CbParams} + end end. + +sig_method(Params) -> + sig_method_1(couch_util:get_value("oauth_signature_method", Params)). +sig_method_1("PLAINTEXT") -> + plaintext; +% sig_method_1("RSA-SHA1") -> +% rsa_sha1; +sig_method_1("HMAC-SHA1") -> + hmac_sha1; +sig_method_1(_) -> + undefined. + + ok(#httpd{mochi_req=MochiReq}, Body) -> {ok, MochiReq:respond({200, [], Body})}. + + +oauth_credentials_info(Token, ConsumerKey) -> + case use_auth_db() of + {ok, Db} -> + Result = case query_oauth_view(Db, [?l2b(ConsumerKey), ?l2b(Token)]) of + [] -> + nil; + [Creds] -> + Creds; + [_ | _] -> + Reason = iolist_to_binary( + io_lib:format("Found multiple OAuth credentials for the pair " + " (consumer_key: `~p`, token: `~p`)", [ConsumerKey, Token])), + {error, {<<"oauth_token_consumer_key_pair">>, Reason}} + end, + couch_db:close(Db), + Result; + nil -> + { + case couch_config:get("oauth_consumer_secrets", ConsumerKey) of + undefined -> []; + ConsumerSecret -> [{<<"consumer_secret">>, ?l2b(ConsumerSecret)}] + end + ++ + case couch_config:get("oauth_token_secrets", Token) of + undefined -> []; + TokenSecret -> [{<<"token_secret">>, ?l2b(TokenSecret)}] + end + ++ + case couch_config:get("oauth_token_users", Token) of + undefined -> []; + User -> [{<<"username">>, ?l2b(User)}] + end + } + end. + + +use_auth_db() -> + case couch_config:get("couch_httpd_oauth", "use_users_db", "false") of + "false" -> + nil; + "true" -> + AuthDb = open_auth_db(), + {ok, _AuthDb2} = ensure_oauth_views_exist(AuthDb) + end. + + +open_auth_db() -> + DbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db")), + DbOptions = [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}], + {ok, AuthDb} = couch_db:open_int(DbName, DbOptions), + AuthDb. + + +ensure_oauth_views_exist(AuthDb) -> + case couch_db:open_doc(AuthDb, ?OAUTH_DDOC_ID, []) of + {ok, _DDoc} -> + {ok, AuthDb}; + _ -> + {ok, DDoc} = get_oauth_ddoc(), + {ok, _Rev} = couch_db:update_doc(AuthDb, DDoc, []), + {ok, _AuthDb2} = couch_db:reopen(AuthDb) + end. + + +get_oauth_ddoc() -> + Json = {[ + {<<"_id">>, ?OAUTH_DDOC_ID}, + {<<"language">>, <<"javascript">>}, + {<<"views">>, + {[ + {?OAUTH_VIEW_NAME, + {[ + {<<"map">>, ?OAUTH_MAP_FUN} + ]} + } + ]} + } + ]}, + {ok, couch_doc:from_json_obj(Json)}. + + +query_oauth_view(Db, Key) -> + ViewOptions = [ + {start_key, Key}, + {end_key, Key} + ], + Callback = fun({row, Row}, Acc) -> + {ok, [couch_util:get_value(value, Row) | Acc]}; + (_, Acc) -> + {ok, Acc} + end, + {ok, Result} = couch_mrview:query_view( + Db, ?OAUTH_DDOC_ID, ?OAUTH_VIEW_NAME, ViewOptions, Callback, []), + Result. http://git-wip-us.apache.org/repos/asf/couchdb/blob/d01faab3/src/couchdb/couch_js_functions.hrl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_js_functions.hrl b/src/couchdb/couch_js_functions.hrl index 1c2f167..36e1512 100644 --- a/src/couchdb/couch_js_functions.hrl +++ b/src/couchdb/couch_js_functions.hrl @@ -131,3 +131,21 @@ } } ">>). + + +-define(OAUTH_MAP_FUN, <<" + function(doc) { + if (doc.type === 'user' && doc.oauth && doc.oauth.consumer_keys) { + for (var consumer_key in doc.oauth.consumer_keys) { + for (var token in doc.oauth.tokens) { + var obj = { + 'consumer_secret': doc.oauth.consumer_keys[consumer_key], + 'token_secret': doc.oauth.tokens[token], + 'username': doc.name + }; + emit([consumer_key, token], obj); + } + } + } + } +">>).