Skip to content
Browse files

Merge pull request #14 from tavisrudd/master

update to 1.3, replace dep on clj-apache-http with clj-http and general cleanup (subsumes pull req #13)
  • Loading branch information...
2 parents cf8d11c + a88fa73 commit 80fdd5d443a07be1850ff215ca267a8c25ecbac1 @mattrepl committed
Showing with 154 additions and 159 deletions.
  1. +1 −0 .gitignore
  2. +4 −9 project.clj
  3. +101 −129 src/oauth/client.clj
  4. +24 −15 src/oauth/signature.clj
  5. +2 −2 test/oauth/client_test.clj
  6. +13 −1 test/oauth/client_twitter_test.clj
  7. +9 −3 test/oauth/signature_test.clj
View
1 .gitignore
@@ -4,4 +4,5 @@ lib
classes
*.jar
pom.xml
+.lein-*
View
13 project.clj
@@ -1,10 +1,5 @@
-(defproject clj-oauth "1.2.10-SNAPSHOT"
+(defproject clj-oauth "1.3.1-SNAPSHOT"
:description "OAuth support for Clojure"
- :dependencies [[org.clojure/clojure "1.2.0"]
- [org.clojure/clojure-contrib "1.2.0"]
- [com.twinql.clojure/clj-apache-http "2.3.1"]
- [org.apache.httpcomponents/httpclient "4.1"]
- [org.apache.httpcomponents/httpcore "4.1"]
- [org.apache.httpcomponents/httpmime "4.1"]]
- :dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]])
-
+ :repositories {"snapshots" {:url "s3p://lein-snapshots/snapshots"}}
+ :dependencies [[org.clojure/clojure "1.3.0"]
+ [clj-http "0.2.7"]])
View
230 src/oauth/client.clj
@@ -1,96 +1,120 @@
-(ns
+(ns
#^{:author "Matt Revelle"
- :doc "OAuth client library for Clojure."}
+ :doc "OAuth client library for Clojure."}
oauth.client
(:require [oauth.digest :as digest]
[oauth.signature :as sig]
- [com.twinql.clojure.http :as http])
- (:use [clojure.contrib.string :only [as-str join split upper-case]]))
-
-(declare success-content
- authorization-header)
-
-(defstruct #^{:doc "OAuth consumer"} consumer
- :key
- :secret
- :request-uri
- :access-uri
- :authorize-uri
- :signature-method)
-
-(defn check-success-response [m]
- (let [code (:code m)]
- (if (or (< code 200)
- (>= code 300))
- (throw (new Exception (str "Got non-success response " code ".")))
- m)))
-
-(defn success-content [m]
- (:content
- (check-success-response m)))
+ [clj-http.client :as httpclient])
+ (:use [clojure.string :only [join split upper-case]]))
+(defrecord #^{:doc "OAuth consumer"}
+ Consumer [key secret request-uri
+ access-uri authorize-uri signature-method])
(defn make-consumer
"Make a consumer struct map."
[key secret request-uri access-uri authorize-uri signature-method]
- (struct consumer
+ (Consumer.
key
secret
- request-uri
- access-uri
- authorize-uri
+ request-uri
+ access-uri
+ authorize-uri
signature-method))
-;;; Parse form-encoded bodies from OAuth responses.
-(defmethod http/entity-as :urldecoded
- [entity as status]
- (into {}
- (if-let [body (http/entity-as entity :string status)]
+(defn user-approval-uri
+ "Builds the URI to the Service Provider where the User will be prompted
+to approve the Consumer's access to their account."
+ [consumer token]
+ (str (:authorize-uri consumer)
+ "?" (httpclient/generate-query-string {:oauth_token token})))
+
+(defn authorization-header
+ "OAuth credentials formatted for the Authorization HTTP header."
+ ([oauth-params]
+ (str "OAuth "
+ (join ", "
+ (map (fn [[k v]]
+ (str (-> k sig/as-str sig/url-encode)
+ "=\"" (-> v sig/as-str sig/url-encode) "\""))
+ oauth-params))))
+ ([oauth-params realm]
+ (authorization-header (assoc oauth-params :realm realm))))
+
+(defn form-decode
+ "Parse form-encoded bodies from OAuth responses."
+ [s]
+ (if s
+ (into {}
(map (fn [kv]
- (let [[k v] (split #"=" kv)
+ (let [[k v] (split kv #"=")
k (or k "")
v (or v "")]
[(keyword (sig/url-decode k)) (sig/url-decode v)]))
- (split #"&" body))
- nil)))
+ (split s #"&")))))
+
+(defn- check-success-response [m]
+ (let [code (:status m)]
+ (if (or (< code 200)
+ (>= code 300))
+ (throw (new Exception (str "Got non-success code: " code ". "
+ "Content: " (:body m))))
+ m)))
+(defn post-request-body-decoded [url & [req]]
+ #_(success-content
+ (http/post (:request-uri consumer)
+ :headers {"Authorization" (authorization-header params)}
+ :parameters (http/map->params {:use-expect-continue false})
+ :as :urldecoded))
+ (form-decode
+ (:body (check-success-response
+ (httpclient/post url req)))))
+
+(defn- oauth-post-request-decoded [url oauth-params & [form-params]]
+ (let [req (merge
+ {:headers {"Authorization" (authorization-header
+ oauth-params)}}
+ (if form-params {:form-params form-params}))]
+ (post-request-body-decoded url req)))
+
+(defn credentials
+ "Return authorization credentials needed for access to protected resources.
+The key-value pairs returned as a map will need to be added to the
+Authorization HTTP header or added as query parameters to the request."
+ ([consumer token token-secret request-method request-uri & [request-params]]
+ (let [unsigned-oauth-params (sig/oauth-params consumer token)
+ unsigned-params (merge request-params
+ unsigned-oauth-params)
+ signature (sig/sign consumer
+ (sig/base-string (-> request-method
+ sig/as-str
+ upper-case)
+ request-uri
+ unsigned-params)
+ token-secret)]
+ (assoc unsigned-oauth-params :oauth_signature signature))))
+
+(defn- get-oauth-token
+ ([consumer uri unsigned-params & [token-secret]]
+ (let [signature (sig/sign consumer
+ (sig/base-string "POST" uri unsigned-params)
+ token-secret)
+ params (assoc unsigned-params :oauth_signature signature)]
+ (oauth-post-request-decoded uri params))))
(defn request-token
"Fetch request token for the consumer."
([consumer]
- (let [unsigned-params (sig/oauth-params consumer)
- signature (sig/sign consumer
- (sig/base-string "POST"
- (:request-uri consumer)
- unsigned-params))
- params (assoc unsigned-params
- :oauth_signature signature)]
- (success-content
- (http/post (:request-uri consumer)
- :headers {"Authorization" (authorization-header params)}
- :parameters (http/map->params {:use-expect-continue false})
- :as :urldecoded))))
- ([consumer callback-uri]
- (let [unsigned-params (assoc (sig/oauth-params consumer)
- :oauth_callback callback-uri)
- signature (sig/sign consumer
- (sig/base-string "POST"
- (:request-uri consumer)
- unsigned-params))
- params (assoc unsigned-params
- :oauth_signature signature)]
- (success-content
- (http/post (:request-uri consumer)
- :headers {"Authorization" (authorization-header params)}
- :parameters (http/map->params {:use-expect-continue false})
- :as :urldecoded)))))
+ (request-token consumer nil))
-(defn user-approval-uri
- "Builds the URI to the Service Provider where the User will be prompted
-to approve the Consumer's access to their account."
- [consumer token]
- (.toString (http/resolve-uri (:authorize-uri consumer)
- {:oauth_token token})))
+ ([consumer callback-uri]
+ (let [unsigned-params (sig/oauth-params consumer)
+ unsigned-params (if callback-uri
+ (assoc unsigned-params
+ :oauth_callback callback-uri)
+ unsigned-params)]
+ (get-oauth-token consumer (:request-uri consumer) unsigned-params))))
-(defn access-token
+(defn access-token
"Exchange a request token for an access token.
When provided with two arguments, this function operates as per OAuth 1.0.
With three arguments, a verifier is used."
@@ -102,19 +126,10 @@ to approve the Consumer's access to their account."
(:oauth_token request-token)
verifier)
(sig/oauth-params consumer
- (:oauth_token request-token)))
- signature (sig/sign consumer
- (sig/base-string "POST"
- (:access-uri consumer)
- unsigned-params)
- (:oauth_token_secret request-token))
- params (assoc unsigned-params
- :oauth_signature signature)]
- (success-content
- (http/post (:access-uri consumer)
- :headers {"Authorization" (authorization-header params)}
- :parameters (http/map->params {:use-expect-continue false})
- :as :urldecoded)))))
+ (:oauth_token
+ request-token)))
+ token-secret (:oauth_token_secret request-token)]
+ (get-oauth-token consumer (:access-uri consumer) unsigned-params token-secret))))
(defn xauth-access-token
"Request an access token with a username and password with xAuth."
@@ -130,48 +145,5 @@ to approve the Consumer's access to their account."
post-params)))
params (assoc oauth-params
:oauth_signature signature)]
- (success-content
- (http/post (:access-uri consumer)
- :query post-params
- :headers {"Authorization" (authorization-header params)}
- :parameters (http/map->params {:use-expect-continue false})
- :as :urldecoded))))
-
-(defn credentials
- "Return authorization credentials needed for access to protected resources.
-The key-value pairs returned as a map will need to be added to the
-Authorization HTTP header or added as query parameters to the request."
- ([consumer token token-secret request-method request-uri & [request-params]]
- (let [unsigned-oauth-params (sig/oauth-params consumer token)
- unsigned-params (merge request-params
- unsigned-oauth-params)
- signature (sig/sign consumer
- (sig/base-string (-> request-method
- as-str
- upper-case)
- request-uri
- unsigned-params)
- token-secret)]
- (assoc unsigned-oauth-params :oauth_signature signature))))
-
-(defn authorization-header
- "OAuth credentials formatted for the Authorization HTTP header."
- ([oauth-params]
- (str "OAuth " (join ", " (map (fn [[k v]]
- (str (-> k as-str sig/url-encode) "=\"" (-> v as-str sig/url-encode) "\""))
- oauth-params))))
- ([oauth-params realm]
- (authorization-header (assoc oauth-params realm))))
-
-(defn check-success-response [m]
- (let [code (:code m)
- reason (:reason m)]
- (if (or (< code 200)
- (>= code 300))
- (throw (new Exception (str "Got non-success code: " code ". "
- "Reason: " reason ", "
- "Content: " (:content m))))
- m)))
-
-(defn success-content [m]
- (:content (check-success-response m)))
+ (oauth-post-request-decoded (:access-uri consumer)
+ params post-params)))
View
39 src/oauth/signature.clj
@@ -1,9 +1,9 @@
-(ns
+(ns
#^{:author "Matt Revelle"
- :doc "OAuth client library for Clojure."}
+ :doc "OAuth client library for Clojure."}
oauth.signature
(:require [oauth.digest :as digest])
- (:use [clojure.contrib.string :only [join as-str]]))
+ (:use [clojure.string :only [join]]))
(declare rand-str
base-string
@@ -11,9 +11,17 @@
url-encode
oauth-params)
+(defn- named? [a]
+ (instance? clojure.lang.Named a))
+
+(defn as-str [a]
+ (if (named? a)
+ (name a)
+ (str a)))
+
(def secure-random (java.security.SecureRandom/getInstance "SHA1PRNG"))
-(defn rand-str
+(defn rand-str
"Random string for OAuth requests."
[length]
(. (new BigInteger (int (* 5 length)) ^java.util.Random secure-random) toString 32))
@@ -24,21 +32,23 @@
(defn url-form-encode [params]
(join "&" (map (fn [[k v]]
- (str (url-encode (as-str k)) "=" (url-encode (as-str v)))) params )))
+ (str (url-encode (as-str k))
+ "=" (url-encode (as-str v)))) params )))
(defn base-string
([method base-url c t params]
- (base-string method base-url (assoc params
- :oauth_consumer_key (:key c)
- :oauth_token (:token t)
- :oauth_signature_method (or (params :oauth_signature_method)
- (signature-methods (:signature-method c)))
- :oauth_version "1.0")))
+ (base-string method base-url
+ (assoc params
+ :oauth_consumer_key (:key c)
+ :oauth_token (:token t)
+ :oauth_signature_method (or (params :oauth_signature_method)
+ (signature-methods (:signature-method c)))
+ :oauth_version "1.0")))
([method base-url params]
(join "&" [method
- (url-encode base-url)
+ (url-encode base-url)
(url-encode (url-form-encode (sort params)))])))
-(defmulti sign
+(defmulti sign
"Sign a base string for authentication."
{:arglists '([consumer base-string & [token-secret]])}
(fn [c & r] (:signature-method c)))
@@ -80,9 +90,8 @@ requires RFC 3986 encoding."
:oauth_nonce (rand-str 30)
:oauth_version "1.0"})
([consumer token]
- (assoc (oauth-params consumer)
+ (assoc (oauth-params consumer)
:oauth_token token))
([consumer token verifier]
(assoc (oauth-params consumer token)
:oauth_verifier (str verifier))))
-
View
4 test/oauth/client_test.clj
@@ -3,7 +3,7 @@
[oauth.signature :as sig]
:reload-all)
(:use clojure.test
- [clojure.contrib.pprint :only [pprint]]))
+ [clojure.pprint :only [pprint]]))
(deftest ^{:doc "Test creation of authorization header for request_token access."}
request-access-authorization-header
@@ -68,4 +68,4 @@
t "nnch734d00sl2jdk"]
;; The approval URL should only use the :oauth_token in the User approval URI
(is (= "https://api.twitter.com/oauth/authorize?oauth_token=nnch734d00sl2jdk"
- (oc/user-approval-uri c t)))))
+ (oc/user-approval-uri c t)))))
View
14 test/oauth/client_twitter_test.clj
@@ -1,4 +1,5 @@
(ns oauth.client-twitter-test
+ (:refer-clojure :exclude [key])
(:require [oauth.client :as oc])
(:use clojure.test)
(:load "twitter_keys"))
@@ -13,4 +14,15 @@
#^{:doc "Test requesting a token from Twitter.
Considered to pass if no exception is thrown."}
request-token
- (oc/request-token consumer))
+ (oc/request-token consumer))
+
+(deftest
+ #^{:doc "Considered to pass if no exception is thrown."}
+ user-approval-uri
+ (is (instance? String (oc/user-approval-uri consumer (oc/request-token consumer)))))
+
+#_(deftest
+ #^{:doc "Considered to pass if no exception is thrown."}
+ access-token
+ (let [request-token (oc/request-token consumer)]
+ (oc/access-token consumer request-token ...verifier...)))
View
12 test/oauth/signature_test.clj
@@ -12,6 +12,12 @@
:oauth_version "1.0"})
(deftest
+ as-str-function
+ (is (= (sig/as-str :hello) "hello"))
+ (is (= (sig/as-str 3412) "3412"))
+ (is (= (sig/as-str 'hello) "hello")))
+
+(deftest
signature-methods
(is (= (sig/signature-methods :hmac-sha1) "HMAC-SHA1")))
@@ -242,11 +248,11 @@
url-form-encode
(is (= (sig/url-form-encode {}) ""))
(is (= (sig/url-form-encode {"hello" "there"}) "hello=there"))
- (is (= (sig/url-form-encode {"hello" "there" "name" "Bill" }) "hello=there&name=Bill"))
+ (is (= (sig/url-form-encode (sort {"hello" "there" "name" "Bill" })) "hello=there&name=Bill"))
(is (= (sig/url-form-encode {:hello "there"}) "hello=there"))
- (is (= (sig/url-form-encode {:hello "there" :name "Bill" }) "hello=there&name=Bill"))
+ (is (= (sig/url-form-encode (sort {:hello "there" :name "Bill" })) "hello=there&name=Bill"))
(is (= (sig/url-form-encode {:hello "there"}) "hello=there"))
- (is (= (sig/url-form-encode {:hello "there" :name "Bill Smith" }) "hello=there&name=Bill%20Smith")))
+ (is (= (sig/url-form-encode (sort {:hello "there" :name "Bill Smith" })) "hello=there&name=Bill%20Smith")))

0 comments on commit 80fdd5d

Please sign in to comment.
Something went wrong with that request. Please try again.