You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by fd...@apache.org on 2012/01/05 19:51:32 UTC
[3/3] git commit: Allow OAuth credentials to be stored in user
documents
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/master
Commit: d01faab3f464ff0806f4ad9f4166ca7a498a4866
Parents: 75b6e09
Author: Filipe David Borba Manana <fd...@apache.org>
Authored: Wed Jan 4 15:51:00 2012 +0000
Committer: Filipe David Borba Manana <fd...@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);
+ }
+ }
+ }
+ }
+">>).