diff options
Diffstat (limited to 'deps/oauth/src')
-rw-r--r-- | deps/oauth/src/oauth.app.src | 21 | ||||
-rw-r--r-- | deps/oauth/src/oauth.erl | 107 | ||||
-rw-r--r-- | deps/oauth/src/oauth_client.erl | 149 | ||||
-rw-r--r-- | deps/oauth/src/oauth_hmac_sha1.erl | 11 | ||||
-rw-r--r-- | deps/oauth/src/oauth_http.erl | 22 | ||||
-rw-r--r-- | deps/oauth/src/oauth_plaintext.erl | 10 | ||||
-rw-r--r-- | deps/oauth/src/oauth_rsa_sha1.erl | 30 | ||||
-rw-r--r-- | deps/oauth/src/oauth_unix.erl | 16 | ||||
-rw-r--r-- | deps/oauth/src/oauth_uri.erl | 98 |
9 files changed, 464 insertions, 0 deletions
diff --git a/deps/oauth/src/oauth.app.src b/deps/oauth/src/oauth.app.src new file mode 100644 index 00000000..9ff22b1a --- /dev/null +++ b/deps/oauth/src/oauth.app.src @@ -0,0 +1,21 @@ +{application, oauth, [ + {description, "An Erlang OAuth 1.0 implementation"}, + {vsn, git}, + {modules, [ + oauth, + oauth_client, + oauth_hmac_sha1, + oauth_http, + oauth_plaintext, + oauth_rsa_sha1, + oauth_unix, + oauth_uri + ]}, + {registered, []}, + {applications, [ + kernel, + stdlib, + crypto, + inets + ]} +]}. diff --git a/deps/oauth/src/oauth.erl b/deps/oauth/src/oauth.erl new file mode 100644 index 00000000..866655c9 --- /dev/null +++ b/deps/oauth/src/oauth.erl @@ -0,0 +1,107 @@ +-module(oauth). + +-export( + [ get/5 + , header/1 + , post/5 + , signature/5 + , signature_base_string/3 + , signed_params/6 + , token/1 + , token_secret/1 + , uri/2 + , verify/6 + ]). + + +get(URL, ExtraParams, Consumer, Token, TokenSecret) -> + SignedParams = signed_params("GET", URL, ExtraParams, Consumer, Token, TokenSecret), + oauth_http:get(uri(URL, SignedParams)). + +post(URL, ExtraParams, Consumer, Token, TokenSecret) -> + SignedParams = signed_params("POST", URL, ExtraParams, Consumer, Token, TokenSecret), + oauth_http:post(URL, oauth_uri:params_to_string(SignedParams)). + +uri(Base, []) -> + Base; +uri(Base, Params) -> + lists:concat([Base, "?", oauth_uri:params_to_string(Params)]). + +header(Params) -> + {"Authorization", "OAuth " ++ oauth_uri:params_to_header_string(Params)}. + +token(Params) -> + proplists:get_value("oauth_token", Params). + +token_secret(Params) -> + proplists:get_value("oauth_token_secret", Params). + +verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret) -> + case signature_method(Consumer) of + plaintext -> + oauth_plaintext:verify(Signature, consumer_secret(Consumer), TokenSecret); + hmac_sha1 -> + BaseString = signature_base_string(HttpMethod, URL, Params), + oauth_hmac_sha1:verify(Signature, BaseString, consumer_secret(Consumer), TokenSecret); + rsa_sha1 -> + BaseString = signature_base_string(HttpMethod, URL, Params), + oauth_rsa_sha1:verify(Signature, BaseString, consumer_secret(Consumer)) + end. + +signed_params(HttpMethod, URL, ExtraParams, Consumer, Token, TokenSecret) -> + Params = token_param(Token, params(Consumer, ExtraParams)), + [{"oauth_signature", signature(HttpMethod, URL, Params, Consumer, TokenSecret)}|Params]. + +signature(HttpMethod, URL, Params, Consumer, TokenSecret) -> + case signature_method(Consumer) of + plaintext -> + oauth_plaintext:signature(consumer_secret(Consumer), TokenSecret); + hmac_sha1 -> + BaseString = signature_base_string(HttpMethod, URL, Params), + oauth_hmac_sha1:signature(BaseString, consumer_secret(Consumer), TokenSecret); + rsa_sha1 -> + BaseString = signature_base_string(HttpMethod, URL, Params), + oauth_rsa_sha1:signature(BaseString, consumer_secret(Consumer)) + end. + +signature_base_string(HttpMethod, URL, Params) -> + NormalizedURL = oauth_uri:normalize(URL), + NormalizedParams = oauth_uri:params_to_string(lists:sort(Params)), + oauth_uri:calate("&", [HttpMethod, NormalizedURL, NormalizedParams]). + +token_param("", Params) -> + Params; +token_param(Token, Params) -> + [{"oauth_token", Token}|Params]. + +params(Consumer, Params) -> + Nonce = base64:encode_to_string(crypto:rand_bytes(32)), % cf. ruby-oauth + params(Consumer, oauth_unix:timestamp(), Nonce, Params). + +params(Consumer, Timestamp, Nonce, Params) -> + [ {"oauth_version", "1.0"} + , {"oauth_nonce", Nonce} + , {"oauth_timestamp", integer_to_list(Timestamp)} + , {"oauth_signature_method", signature_method_string(Consumer)} + , {"oauth_consumer_key", consumer_key(Consumer)} + | Params + ]. + +signature_method_string(Consumer) -> + case signature_method(Consumer) of + plaintext -> + "PLAINTEXT"; + hmac_sha1 -> + "HMAC-SHA1"; + rsa_sha1 -> + "RSA-SHA1" + end. + +signature_method(_Consumer={_, _, Method}) -> + Method. + +consumer_secret(_Consumer={_, Secret, _}) -> + Secret. + +consumer_key(_Consumer={Key, _, _}) -> + Key. diff --git a/deps/oauth/src/oauth_client.erl b/deps/oauth/src/oauth_client.erl new file mode 100644 index 00000000..2bd24c03 --- /dev/null +++ b/deps/oauth/src/oauth_client.erl @@ -0,0 +1,149 @@ +-module(oauth_client). + +-behaviour(gen_server). + +-export([access_token_params/1, deauthorize/1, get/2, get/3, get/4, get_access_token/2, + get_access_token/3, get_access_token/4, get_request_token/2, get_request_token/3, + get_request_token/4, start/1, start/2, start_link/1, start_link/2, stop/1]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]). + +%%============================================================================ +%% API functions +%%============================================================================ + +start(Consumer) -> + gen_server:start(?MODULE, Consumer, []). + +start(ServerName, Consumer) -> + gen_server:start(ServerName, ?MODULE, Consumer, []). + +start_link(Consumer) -> + gen_server:start_link(?MODULE, Consumer, []). + +start_link(ServerName, Consumer) -> + gen_server:start_link(ServerName, ?MODULE, Consumer, []). + +get_request_token(Client, URL) -> + get_request_token(Client, URL, [], header). + +get_request_token(Client, URL, Params) -> + gen_server:call(Client, {get_request_token, URL, Params, header}). + +get_request_token(Client, URL, Params, ParamsMethod) -> + gen_server:call(Client, {get_request_token, URL, Params, ParamsMethod}). + +get_access_token(Client, URL) -> + get_access_token(Client, URL, [], header). + +get_access_token(Client, URL, Params) -> + gen_server:call(Client, {get_access_token, URL, Params, header}). + +get_access_token(Client, URL, Params, ParamsMethod) -> + gen_server:call(Client, {get_access_token, URL, Params, ParamsMethod}). + +get(Client, URL) -> + get(Client, URL, [], header). + +get(Client, URL, Params) -> + gen_server:call(Client, {get, URL, Params, header}). + +get(Client, URL, Params, ParamsMethod) -> + gen_server:call(Client, {get, URL, Params, ParamsMethod}). + +access_token_params(Client) -> + gen_server:call(Client, {access_token_params}). + +deauthorize(Client) -> + gen_server:cast(Client, deauthorize). + +stop(Client) -> + gen_server:cast(Client, stop). + +%%============================================================================ +%% Helper functions +%%============================================================================ + +oauth_get(header, URL, Params, Consumer, Token, TokenSecret) -> + Signed = oauth:signed_params("GET", URL, Params, Consumer, Token, TokenSecret), + {AuthorizationParams, QueryParams} = lists:partition(fun({K, _}) -> lists:prefix("oauth_", K) end, Signed), + Request = {oauth:uri(URL, QueryParams), [oauth:header(AuthorizationParams)]}, + httpc:request(get, Request, [{autoredirect, false}], []); +oauth_get(querystring, URL, Params, Consumer, Token, TokenSecret) -> + oauth:get(URL, Params, Consumer, Token, TokenSecret). + +%%============================================================================ +%% gen_server callbacks +%%============================================================================ + +init(Consumer) -> + {ok, {Consumer}}. + +handle_call({get_request_token, URL, Params, ParamsMethod}, _From, State={Consumer}) -> + case oauth_get(ParamsMethod, URL, Params, Consumer, "", "") of + {ok, Response} -> + case oauth_http:response_code(Response) of + 200 -> + RParams = oauth_http:response_params(Response), + {reply, {ok, oauth:token(RParams)}, {Consumer, RParams}}; + _ -> + {reply, Response, State} + end; + Error -> + {reply, Error, State} + end; +handle_call({get_access_token, URL, Params, ParamsMethod}, _From, State={Consumer, RParams}) -> + case oauth_get(ParamsMethod, URL, Params, Consumer, oauth:token(RParams), oauth:token_secret(RParams)) of + {ok, Response} -> + case oauth_http:response_code(Response) of + 200 -> + AParams = oauth_http:response_params(Response), + {reply, ok, {Consumer, RParams, AParams}}; + _ -> + {reply, Response, State} + end; + Error -> + {reply, Error, State} + end; +handle_call({get, URL, Params, ParamsMethod}, _From, State={Consumer, _RParams, AParams}) -> + case oauth_get(ParamsMethod, URL, Params, Consumer, oauth:token(AParams), oauth:token_secret(AParams)) of + {ok, Response={{_, Status, _}, Headers, Body}} -> + case Status of + 200 -> + case proplists:get_value("content-type", Headers) of + undefined -> + {reply, {ok, Headers, Body}, State}; + ContentType -> + MediaType = hd(string:tokens(ContentType, ";")), + case lists:suffix("/xml", MediaType) orelse lists:suffix("+xml", MediaType) of + true -> + {XML, []} = xmerl_scan:string(Body), + {reply, {ok, Headers, XML}, State}; + false -> + {reply, {ok, Headers, Body}, State} + end + end; + _ -> + {reply, Response, State} + end; + Error -> + {reply, Error, State} + end; +handle_call({access_token_params}, _From, State={_Consumer, _RParams, AParams}) -> + {reply, AParams, State}. + +handle_cast(deauthorize, {Consumer, _RParams}) -> + {noreply, {Consumer}}; +handle_cast(deauthorize, {Consumer, _RParams, _AParams}) -> + {noreply, {Consumer}}; +handle_cast(stop, State) -> + {stop, normal, State}. + +handle_info(_Msg, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +terminate(normal, _State) -> + ok. diff --git a/deps/oauth/src/oauth_hmac_sha1.erl b/deps/oauth/src/oauth_hmac_sha1.erl new file mode 100644 index 00000000..79d59f37 --- /dev/null +++ b/deps/oauth/src/oauth_hmac_sha1.erl @@ -0,0 +1,11 @@ +-module(oauth_hmac_sha1). + +-export([signature/3, verify/4]). + + +signature(BaseString, CS, TS) -> + Key = oauth_uri:calate("&", [CS, TS]), + base64:encode_to_string(crypto:sha_mac(Key, BaseString)). + +verify(Signature, BaseString, CS, TS) -> + couch_util:verify(signature(BaseString, CS, TS), Signature). diff --git a/deps/oauth/src/oauth_http.erl b/deps/oauth/src/oauth_http.erl new file mode 100644 index 00000000..92c806cc --- /dev/null +++ b/deps/oauth/src/oauth_http.erl @@ -0,0 +1,22 @@ +-module(oauth_http). + +-export([get/1, post/2, response_params/1, response_body/1, response_code/1]). + + +get(URL) -> + request(get, {URL, []}). + +post(URL, Data) -> + request(post, {URL, [], "application/x-www-form-urlencoded", Data}). + +request(Method, Request) -> + httpc:request(Method, Request, [{autoredirect, false}], []). + +response_params(Response) -> + oauth_uri:params_from_string(response_body(Response)). + +response_body({{_, _, _}, _, Body}) -> + Body. + +response_code({{_, Code, _}, _, _}) -> + Code. diff --git a/deps/oauth/src/oauth_plaintext.erl b/deps/oauth/src/oauth_plaintext.erl new file mode 100644 index 00000000..41a1e9b2 --- /dev/null +++ b/deps/oauth/src/oauth_plaintext.erl @@ -0,0 +1,10 @@ +-module(oauth_plaintext). + +-export([signature/2, verify/3]). + + +signature(CS, TS) -> + oauth_uri:calate("&", [CS, TS]). + +verify(Signature, CS, TS) -> + couch_util:verify(signature(CS, TS), Signature). diff --git a/deps/oauth/src/oauth_rsa_sha1.erl b/deps/oauth/src/oauth_rsa_sha1.erl new file mode 100644 index 00000000..6f4828e0 --- /dev/null +++ b/deps/oauth/src/oauth_rsa_sha1.erl @@ -0,0 +1,30 @@ +-module(oauth_rsa_sha1). + +-export([signature/2, verify/3]). + +-include_lib("public_key/include/public_key.hrl"). + + +signature(BaseString, PrivateKeyPath) -> + {ok, [Info]} = public_key:pem_to_der(PrivateKeyPath), + {ok, PrivateKey} = public_key:decode_private_key(Info), + base64:encode_to_string(public_key:sign(list_to_binary(BaseString), PrivateKey)). + +verify(Signature, BaseString, PublicKey) -> + public_key:verify_signature(to_binary(BaseString), sha, base64:decode(Signature), public_key(PublicKey)). + +to_binary(Term) when is_list(Term) -> + list_to_binary(Term); +to_binary(Term) when is_binary(Term) -> + Term. + +public_key(Path) when is_list(Path) -> + {ok, [{cert, DerCert, not_encrypted}]} = public_key:pem_to_der(Path), + {ok, Cert} = public_key:pkix_decode_cert(DerCert, otp), + public_key(Cert); +public_key(#'OTPCertificate'{tbsCertificate=Cert}) -> + public_key(Cert); +public_key(#'OTPTBSCertificate'{subjectPublicKeyInfo=Info}) -> + public_key(Info); +public_key(#'OTPSubjectPublicKeyInfo'{subjectPublicKey=Key}) -> + Key. diff --git a/deps/oauth/src/oauth_unix.erl b/deps/oauth/src/oauth_unix.erl new file mode 100644 index 00000000..73ca3143 --- /dev/null +++ b/deps/oauth/src/oauth_unix.erl @@ -0,0 +1,16 @@ +-module(oauth_unix). + +-export([timestamp/0]). + + +timestamp() -> + timestamp(calendar:universal_time()). + +timestamp(DateTime) -> + seconds(DateTime) - epoch(). + +epoch() -> + seconds({{1970,1,1},{00,00,00}}). + +seconds(DateTime) -> + calendar:datetime_to_gregorian_seconds(DateTime). diff --git a/deps/oauth/src/oauth_uri.erl b/deps/oauth/src/oauth_uri.erl new file mode 100644 index 00000000..5023f983 --- /dev/null +++ b/deps/oauth/src/oauth_uri.erl @@ -0,0 +1,98 @@ +-module(oauth_uri). + +-export([normalize/1, calate/2, encode/1]). +-export([params_from_string/1, params_to_string/1, + params_from_header_string/1, params_to_header_string/1]). + +-import(lists, [concat/1]). + + +normalize(URI) -> + case http_uri:parse(URI) of + {Scheme, UserInfo, Host, Port, Path, _Query} -> + normalize(Scheme, UserInfo, string:to_lower(Host), Port, [Path]); + Else -> + Else + end. + +normalize(http, UserInfo, Host, 80, Acc) -> + normalize(http, UserInfo, [Host|Acc]); +normalize(https, UserInfo, Host, 443, Acc) -> + normalize(https, UserInfo, [Host|Acc]); +normalize(Scheme, UserInfo, Host, Port, Acc) -> + normalize(Scheme, UserInfo, [Host, ":", Port|Acc]). + +normalize(Scheme, [], Acc) -> + concat([Scheme, "://"|Acc]); +normalize(Scheme, UserInfo, Acc) -> + concat([Scheme, "://", UserInfo, "@"|Acc]). + +params_to_header_string(Params) -> + intercalate(", ", [concat([encode(K), "=\"", encode(V), "\""]) || {K, V} <- Params]). + +params_from_header_string(String) -> + [param_from_header_string(Param) || Param <- re:split(String, ",\\s*", [{return, list}]), Param =/= ""]. + +param_from_header_string(Param) -> + [Key, QuotedValue] = string:tokens(Param, "="), + Value = string:substr(QuotedValue, 2, length(QuotedValue) - 2), + {decode(Key), decode(Value)}. + +params_from_string(Params) -> + [param_from_string(Param) || Param <- string:tokens(Params, "&")]. + +param_from_string(Param) -> + list_to_tuple([decode(Value) || Value <- string:tokens(Param, "=")]). + +params_to_string(Params) -> + intercalate("&", [calate("=", [K, V]) || {K, V} <- Params]). + +calate(Sep, Xs) -> + intercalate(Sep, [encode(X) || X <- Xs]). + +intercalate(Sep, Xs) -> + concat(intersperse(Sep, Xs)). + +intersperse(_, []) -> []; +intersperse(_, [X]) -> [X]; +intersperse(Sep, [X|Xs]) -> + [X, Sep|intersperse(Sep, Xs)]. + +-define(is_alphanum(C), C >= $A, C =< $Z; C >= $a, C =< $z; C >= $0, C =< $9). + +encode(Term) when is_integer(Term) -> + integer_to_list(Term); +encode(Term) when is_atom(Term) -> + encode(atom_to_list(Term)); +encode(Term) when is_list(Term) -> + encode(lists:reverse(Term, []), []). + +encode([X | T], Acc) when ?is_alphanum(X); X =:= $-; X =:= $_; X =:= $.; X =:= $~ -> + encode(T, [X | Acc]); +encode([X | T], Acc) -> + NewAcc = [$%, dec2hex(X bsr 4), dec2hex(X band 16#0f) | Acc], + encode(T, NewAcc); +encode([], Acc) -> + Acc. + +decode(Str) when is_list(Str) -> + decode(Str, []). + +decode([$%, A, B | T], Acc) -> + decode(T, [(hex2dec(A) bsl 4) + hex2dec(B) | Acc]); +decode([X | T], Acc) -> + decode(T, [X | Acc]); +decode([], Acc) -> + lists:reverse(Acc, []). + +-compile({inline, [{dec2hex, 1}, {hex2dec, 1}]}). + +dec2hex(N) when N >= 10 andalso N =< 15 -> + N + $A - 10; +dec2hex(N) when N >= 0 andalso N =< 9 -> + N + $0. + +hex2dec(C) when C >= $A andalso C =< $F -> + C - $A + 10; +hex2dec(C) when C >= $0 andalso C =< $9 -> + C - $0. |