diff options
Diffstat (limited to 'src/couch/src/couch_httpd_oauth.erl')
-rw-r--r-- | src/couch/src/couch_httpd_oauth.erl | 391 |
1 files changed, 0 insertions, 391 deletions
diff --git a/src/couch/src/couch_httpd_oauth.erl b/src/couch/src/couch_httpd_oauth.erl deleted file mode 100644 index 03107525b..000000000 --- a/src/couch/src/couch_httpd_oauth.erl +++ /dev/null @@ -1,391 +0,0 @@ -% 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. - --module(couch_httpd_oauth). - --include_lib("couch/include/couch_db.hrl"). --include_lib("couch/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">>). - --record(callback_params, { - consumer, - token, - token_secret, - url, - signature, - params, - username -}). - -% OAuth auth handler using per-node user db -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 = Token, - 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 -> - couch_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:sign(Method, Url, Params, Consumer, Token, - TokenSecret)]), - Req - end. - - -% Look up the consumer key and get the roles to give the consumer -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 -> - couch_log:debug("OAuth handler: user `~p` credentials not found", - [Name]), - Req; - {ok, User, _AuthCtx} -> - 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}=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'}=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) -> - couch_httpd:send_method_not_allowed(Req, "GET"). - -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}=Req1) -> - case Method of - 'GET' -> - % Confirm with the User that they want to authenticate the Consumer - 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(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(Req1, "GET,POST") - end. - -serve_oauth(#httpd{mochi_req=MochiReq}=Req, Fun, FailSilently) -> - % 1. In the HTTP Authorization header as defined in OAuth HTTP Authorization Scheme. - % 2. As the HTTP POST request body with a content-type of application/x-www-form-urlencoded. - % 3. Added to the URLs in the query part (as defined by [RFC3986] section 3). - AuthHeader = case MochiReq:get_header_value("authorization") of - undefined -> - ""; - Else -> - [Head | Tail] = re:split(Else, "\\s", [{parts, 2}, {return, list}]), - case [string:to_lower(Head) | Tail] of - ["oauth", Rest] -> Rest; - _ -> "" - end - end, - HeaderParams = oauth:header_params_decode(AuthHeader), - %Realm = couch_util:get_value("realm", HeaderParams), - - % get requested path - RequestedPath = case MochiReq:get_header_value("x-couchdb-requested-path") of - 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), - - Params = proplists:delete("realm", HeaderParams) ++ mochiweb_util:parse_qs(QueryString), - - couch_log:debug("OAuth Params: ~p", [Params]), - case couch_util:get_value("oauth_version", Params, "1.0") of - "1.0" -> - case couch_util:get_value("oauth_consumer_key", Params, undefined) of - undefined -> - case FailSilently of - true -> Req; - false -> couch_httpd:send_error(Req, 400, <<"invalid_consumer">>, <<"Invalid consumer.">>) - end; - ConsumerKey -> - 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. - - -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 - }, - couch_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 config:get("oauth_consumer_secrets", ConsumerKey) of - undefined -> []; - ConsumerSecret -> [{<<"consumer_secret">>, ?l2b(ConsumerSecret)}] - end - ++ - case config:get("oauth_token_secrets", Token) of - undefined -> []; - TokenSecret -> [{<<"token_secret">>, ?l2b(TokenSecret)}] - end - ++ - case config:get("oauth_token_users", Token) of - undefined -> []; - User -> [{<<"username">>, ?l2b(User)}] - end - } - end. - - -use_auth_db() -> - case 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(config:get("couch_httpd_auth", "authentication_db")), - {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_CTX]), - 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. |