diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..928c1d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +_build +pem2cryptokit +pem2cryptokit.dSYM diff --git a/META b/META index 33ab646..1ed4dd8 100644 --- a/META +++ b/META @@ -1,6 +1,6 @@ name="OOAuth" -version="0.1" +version="0.2" description="OAuth implementation" -requires="cryptokit,netstring,ocurl,netclient" +requires="cryptokit,netstring,curl,netclient,ssl,equeue-ssl" archive(byte) = "ooauth.cma" archive(native) = "ooauth.cmxa" diff --git a/Makefile b/Makefile index e586ce0..cc5a677 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ all: pem2cryptokit ocamlbuild ooauth.cma ooauth.cmxa pem2cryptokit: pem2cryptokit.c - gcc -g -I$(OCAMLDIR) pem2cryptokit.c -L$(OCAMLDIR) -lssl -lcamlrun -lm -o pem2cryptokit + gcc -g -I$(OCAMLDIR) pem2cryptokit.c -L$(OCAMLDIR) -lssl -lcrypto -lcamlrun -lm -o pem2cryptokit install: all ocamlfind install ooauth META $(BFILES) diff --git a/_tags b/_tags index 4003222..bea9055 100644 --- a/_tags +++ b/_tags @@ -1,4 +1,4 @@ <*.ml*> : debug,pkg_cryptokit,pkg_netstring : pkg_curl - : pkg_netclient + : pkg_netclient,pkg_ssl,pkg_equeue-ssl : pkg_netcgi2 diff --git a/oauth_client.ml b/oauth_client.ml index 6e81017..3390be2 100644 --- a/oauth_client.ml +++ b/oauth_client.ml @@ -1,170 +1,194 @@ -module type Http_client = +module type Lwt = sig - val request : - ?http_method:[ `Get | `Head | `Post ] -> - url:string -> - ?headers:(string * string) list -> - ?params:(string * string) list -> - ?body:string * string -> (* content type * body *) - unit -> - Nethttp.http_status * (string * string) list * string + type 'a _r + val bind : 'a _r -> ('a -> 'b _r) -> 'b _r + val return : 'a -> 'a _r + val fail : exn -> 'a _r end -module Make (Http_client : Http_client) = +module Client (Lwt : Lwt) = struct + module type Http_client = + sig + val request : + ?http_method:[ `Get | `Head | `Post | `Delete | `Put of string ] -> + url:string -> + ?headers:(string * string) list -> + ?params:(string * string) list -> + ?body:string * string -> (* content type * body *) + unit -> + (Nethttp.http_status * (string * string) list * string) Lwt._r + end - exception Error of Nethttp.http_status * string + module Make (Http_client : Http_client) = + struct - open Oauth_common + exception Error of Nethttp.http_status * string + open Oauth_common + let (>>=) = Lwt.bind - let authorization_header - ~oauth_version ~oauth_signature_method ~oauth_signature - ~oauth_consumer_key ?oauth_token - ~oauth_timestamp ~oauth_nonce - () = - let params = - [ - "OAuth realm", ""; - "oauth_version", oauth_version; - "oauth_signature_method", string_of_signature_method oauth_signature_method; - "oauth_signature", oauth_signature; - "oauth_consumer_key", oauth_consumer_key; - "oauth_timestamp", string_of_timestamp oauth_timestamp; - "oauth_nonce", oauth_nonce; - ] @ - opt_param "oauth_token" oauth_token in - - "Authorization", - (params |> - List.map (fun (k, v) -> k ^ "=\"" ^ String.escaped (rfc3986_encode v) ^ "\"") |> - String.concat ",") - - - - let parse_response res = - try - let params = Netencoding.Url.dest_url_encoded_parameters res in - (List.assoc "oauth_token" params, List.assoc "oauth_token_secret" params) - with - | _ -> raise (Error (`Internal_server_error, "bad response: " ^ res)) - - - - let fetch_request_token - ?(http_method = `Post) ~url - ?(oauth_version = "1.0") ?(oauth_signature_method = `Hmac_sha1) - ~oauth_consumer_key ~oauth_consumer_secret - ?(oauth_timestamp = make_timestamp ()) ?(oauth_nonce = make_nonce ()) - ?params ?(headers = []) - () = - - let oauth_signature = - sign - ~http_method ~url - ~oauth_version ~oauth_signature_method - ~oauth_consumer_key ~oauth_consumer_secret - ~oauth_timestamp ~oauth_nonce - ?params - () in - - let headers = - authorization_header + let authorization_header ~oauth_version ~oauth_signature_method ~oauth_signature - ~oauth_consumer_key + ~oauth_consumer_key ?oauth_token ~oauth_timestamp ~oauth_nonce - () :: headers in - - let res = - Http_client.request - ~http_method - ~url - ~headers - ?params - () in - - match res with - | (`Ok, _, res) -> parse_response res - | (status, _, res) -> raise (Error (status, res)) - - - - let fetch_access_token - ?(http_method = `Post) ~url - ?(oauth_version = "1.0") ?(oauth_signature_method = `Hmac_sha1) - ~oauth_consumer_key ~oauth_consumer_secret - ~oauth_token ~oauth_token_secret - ?(oauth_timestamp = make_timestamp ()) ?(oauth_nonce = make_nonce ()) - ?(headers = []) - () = - - let oauth_signature = - sign - ~http_method ~url - ~oauth_version ~oauth_signature_method + ?oauth_callback ?oauth_verifier + () = + let params = + [ + "OAuth realm", ""; + "oauth_version", oauth_version; + "oauth_signature_method", string_of_signature_method oauth_signature_method; + "oauth_signature", oauth_signature; + "oauth_consumer_key", oauth_consumer_key; + "oauth_timestamp", string_of_timestamp oauth_timestamp; + "oauth_nonce", oauth_nonce; + ] @ + opt_param "oauth_token" oauth_token @ + opt_param "oauth_callback" oauth_callback @ + opt_param "oauth_verifier" oauth_verifier in + + "Authorization", + (params |> + List.map (fun (k, v) -> k ^ "=\"" ^ String.escaped (rfc3986_encode v) ^ "\"") |> + String.concat ",") + + + + let parse_response res = + try + let params = Netencoding.Url.dest_url_encoded_parameters res in + (List.assoc "oauth_token" params, List.assoc "oauth_token_secret" params) + with + | _ -> raise (Error (`Internal_server_error, "bad response: " ^ res)) + + + + let fetch_request_token + ?(http_method = `Post) ~url + ?(oauth_version = "1.0") ?(oauth_signature_method = `Hmac_sha1) ~oauth_consumer_key ~oauth_consumer_secret - ~oauth_token ~oauth_token_secret - ~oauth_timestamp ~oauth_nonce - () in - - let headers = - authorization_header - ~oauth_version ~oauth_signature_method ~oauth_signature - ~oauth_consumer_key ~oauth_token - ~oauth_timestamp ~oauth_nonce - () :: headers in - - let res = - Http_client.request - ~http_method - ~url - ~headers - () in - - match res with - | (`Ok, _, res) -> parse_response res - | (status, _, res) -> raise (Error (status, res)) - - - - let access_resource - ?(http_method = `Post) ~url - ?(oauth_version = "1.0") ?(oauth_signature_method = `Hmac_sha1) - ~oauth_consumer_key ~oauth_consumer_secret - ~oauth_token ~oauth_token_secret - ?(oauth_timestamp = make_timestamp ()) ?(oauth_nonce = make_nonce ()) - ?params ?(headers = []) ?body - () = - - let oauth_signature = - sign - ~http_method ~url - ~oauth_version ~oauth_signature_method + ?(oauth_timestamp = make_timestamp ()) ?(oauth_nonce = make_nonce ()) + ?(oauth_callback = "oob") + ?params ?(headers = []) + () = + + let oauth_signature = + sign + ~http_method ~url + ~oauth_version ~oauth_signature_method + ~oauth_consumer_key ~oauth_consumer_secret + ~oauth_timestamp ~oauth_nonce + ~oauth_callback ?params + () in + + let headers = + authorization_header + ~oauth_version ~oauth_signature_method ~oauth_signature + ~oauth_consumer_key + ~oauth_timestamp ~oauth_nonce + ~oauth_callback + () :: headers in + + let res = + Http_client.request + ~http_method + ~url + ~headers + ?params + () in + + res >>= (function + | (`Ok, _, res) -> Lwt.return (parse_response res) + | (status, _, res) -> Lwt.fail (Error (status, res))) + + + + let fetch_access_token + ?(http_method = `Post) ~url + ?(oauth_version = "1.0") ?(oauth_signature_method = `Hmac_sha1) ~oauth_consumer_key ~oauth_consumer_secret - ~oauth_token ~oauth_token_secret - ~oauth_timestamp ~oauth_nonce - ?params - () in - - let headers = - authorization_header - ~oauth_version ~oauth_signature_method ~oauth_signature - ~oauth_consumer_key ~oauth_token - ~oauth_timestamp ~oauth_nonce - () :: headers in - - let res = - Http_client.request - ~http_method - ~url - ~headers - ?params - ?body - () in - - match res with - | (`Ok, _, res) -> res - | (status, _, res) -> raise (Error (status, res)) + ~oauth_token ~oauth_token_secret ~oauth_verifier + ?(oauth_timestamp = make_timestamp ()) ?(oauth_nonce = make_nonce ()) + ?(headers = []) + () = + + let oauth_signature = + sign + ~http_method ~url + ~oauth_version ~oauth_signature_method + ~oauth_consumer_key ~oauth_consumer_secret + ~oauth_token ~oauth_token_secret ~oauth_verifier + ~oauth_timestamp ~oauth_nonce + () in + + let headers = + authorization_header + ~oauth_version ~oauth_signature_method ~oauth_signature + ~oauth_consumer_key ~oauth_token ~oauth_verifier + ~oauth_timestamp ~oauth_nonce + () :: headers in + + let res = + Http_client.request + ~http_method + ~url + ~headers + () in + + res >>= (function + | (`Ok, _, res) -> Lwt.return (parse_response res) + | (status, _, res) -> Lwt.fail (Error (status, res))) + + + + let access_resource + ?(http_method = `Post) ~url + ?(oauth_version = "1.0") ?(oauth_signature_method = `Hmac_sha1) + ~oauth_consumer_key ~oauth_consumer_secret + ?oauth_token ?oauth_token_secret + ?(oauth_timestamp = make_timestamp ()) ?(oauth_nonce = make_nonce ()) + ?params ?(headers = []) ?body + () = + + let oauth_signature = + sign + ~http_method ~url + ~oauth_version ~oauth_signature_method + ~oauth_consumer_key ~oauth_consumer_secret + ?oauth_token ?oauth_token_secret + ~oauth_timestamp ~oauth_nonce + ?params + () in + + let headers = + authorization_header + ~oauth_version ~oauth_signature_method ~oauth_signature + ~oauth_consumer_key ?oauth_token + ~oauth_timestamp ~oauth_nonce + () :: headers in + + let res = + Http_client.request + ~http_method + ~url + ~headers + ?params + ?body + () in + + res >>= (function + | (`Ok, _, res) -> Lwt.return res + | (status, _, res) -> Lwt.fail (Error (status, res))) + end +end +module Sync = struct + type 'a _r = 'a + let bind a f = f a + let return a = a + let fail e = raise e end + +include Client(Sync) diff --git a/oauth_client.mli b/oauth_client.mli index 240ab7e..759de0c 100644 --- a/oauth_client.mli +++ b/oauth_client.mli @@ -1,64 +1,80 @@ -module type Http_client = +module type Lwt = sig - val request : - ?http_method:[ `Get | `Head | `Post ] -> - url:string -> - ?headers:(string * string) list -> - ?params:(string * string) list -> - ?body:string * string -> (* content type * body *) - unit -> - Nethttp.http_status * (string * string) list * string + type 'a _r + val bind : 'a _r -> ('a -> 'b _r) -> 'b _r + val return : 'a -> 'a _r + val fail : exn -> 'a _r end -module Make : functor (Http_client : Http_client) -> +module Client : functor (Lwt : Lwt) -> sig + module type Http_client = + sig + val request : + ?http_method:[ `Get | `Head | `Post | `Delete | `Put of string ] -> + url:string -> + ?headers:(string * string) list -> + ?params:(string * string) list -> + ?body:string * string -> (* content type * body *) + unit -> + (Nethttp.http_status * (string * string) list * string) Lwt._r + end - exception Error of Nethttp.http_status * string + module Make : functor (Http_client : Http_client) -> + sig - val fetch_request_token : - ?http_method:[ `Get | `Head | `Post ] -> - url:string -> - ?oauth_version:string -> - ?oauth_signature_method:[ `Plaintext | `Hmac_sha1 | `Rsa_sha1 of Cryptokit.RSA.key ] -> - oauth_consumer_key:string -> - oauth_consumer_secret:string -> - ?oauth_timestamp:float -> - ?oauth_nonce:string -> - ?params:(string * string) list -> - ?headers:(string * string) list -> - unit -> - string * string + exception Error of Nethttp.http_status * string - val fetch_access_token : - ?http_method:[ `Get | `Head | `Post ] -> - url:string -> - ?oauth_version:string -> - ?oauth_signature_method:[ `Plaintext | `Hmac_sha1 | `Rsa_sha1 of Cryptokit.RSA.key ] -> - oauth_consumer_key:string -> - oauth_consumer_secret:string -> - oauth_token:string -> - oauth_token_secret:string -> - ?oauth_timestamp:float -> - ?oauth_nonce:string -> - ?headers:(string * string) list -> - unit -> - string * string + val fetch_request_token : + ?http_method:[ `Get | `Head | `Post | `Delete | `Put of string ] -> + url:string -> + ?oauth_version:string -> + ?oauth_signature_method:[ `Plaintext | `Hmac_sha1 | `Rsa_sha1 of Cryptokit.RSA.key ] -> + oauth_consumer_key:string -> + oauth_consumer_secret:string -> + ?oauth_timestamp:float -> + ?oauth_nonce:string -> + ?oauth_callback:string -> + ?params:(string * string) list -> + ?headers:(string * string) list -> + unit -> + (string * string) Lwt._r - val access_resource : - ?http_method:[ `Get | `Head | `Post ] -> - url:string -> - ?oauth_version:string -> - ?oauth_signature_method:[ `Plaintext | `Hmac_sha1 | `Rsa_sha1 of Cryptokit.RSA.key ] -> - oauth_consumer_key:string -> - oauth_consumer_secret:string -> - oauth_token:string -> - oauth_token_secret:string -> - ?oauth_timestamp:float -> - ?oauth_nonce:string -> - ?params:(string * string) list -> - ?headers:(string * string) list -> - ?body:string * string -> (* content type * body *) - unit -> - string + val fetch_access_token : + ?http_method:[ `Get | `Head | `Post | `Delete | `Put of string ] -> + url:string -> + ?oauth_version:string -> + ?oauth_signature_method:[ `Plaintext | `Hmac_sha1 | `Rsa_sha1 of Cryptokit.RSA.key ] -> + oauth_consumer_key:string -> + oauth_consumer_secret:string -> + oauth_token:string -> + oauth_token_secret:string -> + oauth_verifier:string -> + ?oauth_timestamp:float -> + ?oauth_nonce:string -> + ?headers:(string * string) list -> + unit -> + (string * string) Lwt._r + val access_resource : + ?http_method:[ `Get | `Head | `Post | `Delete | `Put of string ] -> + url:string -> + ?oauth_version:string -> + ?oauth_signature_method:[ `Plaintext | `Hmac_sha1 | `Rsa_sha1 of Cryptokit.RSA.key ] -> + oauth_consumer_key:string -> + oauth_consumer_secret:string -> + ?oauth_token:string -> + ?oauth_token_secret:string -> + ?oauth_timestamp:float -> + ?oauth_nonce:string -> + ?params:(string * string) list -> + ?headers:(string * string) list -> + ?body:string * string -> (* content type * body *) + unit -> + string Lwt._r + end end + +module Sync : Lwt with type 'a _r = 'a + +include module type of Client(Sync) diff --git a/oauth_common.ml b/oauth_common.ml index e0ca6d5..51e8259 100644 --- a/oauth_common.ml +++ b/oauth_common.ml @@ -5,15 +5,17 @@ let opt_param name param = | None -> [] | Some p -> [name, p] -let rng = Cryptokit.Random.device_rng "/dev/random" +let rng = Cryptokit.Random.(pseudo_rng (string (device_rng "/dev/urandom") 20)) -let rfc3986_encode s = Netencoding.Url.encode s +let rfc3986_encode s = Netencoding.Url.encode ~plus:false s let rfc3986_decode s = Netencoding.Url.decode s let string_of_http_method = function | `Get -> "GET" | `Post -> "POST" | `Head -> "HEAD" + | `Delete -> "DELETE" + | `Put _ -> "PUT" let string_of_signature_method = function | `Plaintext -> "PLAINTEXT" @@ -88,7 +90,7 @@ let signature_base_string ~oauth_consumer_key ~oauth_consumer_secret ?oauth_token ?oauth_token_secret ~oauth_timestamp ~oauth_nonce ~oauth_version - ?(params = []) + ?oauth_callback ?oauth_verifier ?(params = []) () = let params = [ @@ -96,9 +98,11 @@ let signature_base_string "oauth_consumer_key", oauth_consumer_key; "oauth_timestamp", string_of_timestamp oauth_timestamp; "oauth_nonce", oauth_nonce; - "oauth_version", oauth_version; + "oauth_version", oauth_version; ] @ opt_param "oauth_token" oauth_token @ + opt_param "oauth_callback" oauth_callback @ + opt_param "oauth_verifier" oauth_verifier @ List.filter (fun (k, v) -> k <> "oauth_signature") params in List.map rfc3986_encode @@ -124,7 +128,7 @@ let sign ~oauth_consumer_key ~oauth_consumer_secret ?oauth_token ?oauth_token_secret ~oauth_timestamp ~oauth_nonce ~oauth_version - ?params + ?oauth_callback ?oauth_verifier ?params () = let key = @@ -140,11 +144,11 @@ let sign ~oauth_consumer_key ~oauth_consumer_secret ?oauth_token ?oauth_token_secret ~oauth_timestamp ~oauth_nonce ~oauth_version - ?params + ?oauth_callback ?oauth_verifier ?params () in match oauth_signature_method with - | `Plaintext -> rfc3986_encode key + | `Plaintext -> key | `Hmac_sha1 -> hmac_sha1_hash signature_base_string key | `Rsa_sha1 rsa_key -> rsa_sha1_hash signature_base_string rsa_key @@ -176,6 +180,6 @@ let check_signature () in match oauth_signature_method with - | `Plaintext -> rfc3986_encode key = oauth_signature + | `Plaintext -> key = oauth_signature | `Hmac_sha1 -> hmac_sha1_hash signature_base_string key = oauth_signature | `Rsa_sha1 rsa_key -> check_rsa_sha1_hash signature_base_string rsa_key oauth_signature diff --git a/oauth_netcgi_http.ml b/oauth_netcgi_http.ml index d464e6f..0382b03 100644 --- a/oauth_netcgi_http.ml +++ b/oauth_netcgi_http.ml @@ -7,7 +7,8 @@ let http_method (cgi : Netcgi.cgi_activation) = | `GET -> `Get | `HEAD -> `Head | `POST -> `Post - | `DELETE | `PUT _ -> raise (Error (`Method_not_allowed, "")) + | `DELETE -> `Delete + | `PUT a -> `Put a#value let url (cgi : Netcgi.cgi_activation) = cgi#url () diff --git a/oauth_netclient_http_client.ml b/oauth_netclient_http_client.ml index 1fa2659..34d5dce 100644 --- a/oauth_netclient_http_client.ml +++ b/oauth_netclient_http_client.ml @@ -6,27 +6,37 @@ let request ?body () = let call = + let make_url () = + let query = Netencoding.Url.mk_url_encoded_parameters params in + url ^ (if query <> "" then "?" ^ query else "") + in match http_method, body with | `Post, None -> new Http_client.post url params | `Post, Some (content_type, body) -> - let query = Netencoding.Url.mk_url_encoded_parameters params in - let url = url ^ (if query <> "" then "?" ^ query else "") in - let call = new Http_client.post_raw url body in + let call = new Http_client.post_raw (make_url ()) body in (call#request_header `Base)#update_field "Content-type" content_type; call - | `Get, _ | `Head, _ -> - let query = Netencoding.Url.mk_url_encoded_parameters params in - let url = url ^ (if query <> "" then "?" ^ query else "") in - match http_method with - | `Get -> new Http_client.get url - | `Head -> new Http_client.head url - | `Post -> assert false in - + | `Delete, _ -> + new Http_client.delete (make_url ()) + | `Put v, None -> + new Http_client.put (make_url ()) v + | `Put _, Some _ -> + failwith "`Put error --> do not set body" + | `Get, _ -> + new Http_client.get (make_url ()) + | `Head, _ -> + new Http_client.head (make_url ()) + in let h = call#request_header `Base in List.iter (fun (k,v) -> h#update_field k v) headers; let pipeline = new Http_client.pipeline in + Ssl.init (); + let ctx = Ssl.create_context Ssl.TLSv1 Ssl.Client_context in + let tct = (Https_client.https_transport_channel_type ctx :> Http_client.transport_channel_type) in + pipeline#configure_transport Http_client.https_cb_id tct; + (* pipeline#set_proxy "localhost" 9888; let url = Neturl.parse_url url in diff --git a/oauth_ocurl_http_client.ml b/oauth_ocurl_http_client.ml index 53e5e59..d38740b 100644 --- a/oauth_ocurl_http_client.ml +++ b/oauth_ocurl_http_client.ml @@ -25,6 +25,7 @@ let request Curl.set_postfields oc body; Curl.set_postfieldsize oc (String.length body); ("Content-type", content_type)::headers + | `Delete, _ | `Put _, _ -> failwith "`Delete | `Put not implemented yet" | `Get, _ | `Head, _ -> let url = url ^ (if query <> "" then "?" ^ query else "") in Curl.set_url oc url; diff --git a/oauth_server.ml b/oauth_server.ml index 2c52a23..a09f83b 100644 --- a/oauth_server.ml +++ b/oauth_server.ml @@ -1,7 +1,7 @@ module type Http = sig type request - val http_method : request -> [ `Get | `Post | `Head ] + val http_method : request -> [ `Get | `Post | `Head | `Delete | `Put of string ] val url : request -> string val header : request -> string -> string (* throws Not_found *) val argument : request -> ?default:string -> string -> string (* throws Not_found *) diff --git a/oauth_server.mli b/oauth_server.mli index 576c136..518abab 100644 --- a/oauth_server.mli +++ b/oauth_server.mli @@ -1,7 +1,7 @@ module type Http = sig type request - val http_method : request -> [ `Get | `Post | `Head ] + val http_method : request -> [ `Get | `Post | `Head | `Delete | `Put of string ] val url : request -> string val header : request -> string -> string (* throws Not_found *) val argument : request -> ?default:string -> string -> string (* throws Not_found *)