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);
+                }
+            }
+        }
+    }
+">>).