diff --git a/.gitignore b/.gitignore index 8c61de9..7e4dcc0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ test/oauth/twitter_keys.clj *.jar pom.xml .lein-* +/.idea +/*.iml +/.clj-kondo/.cache diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..660bc85 --- /dev/null +++ b/deps.edn @@ -0,0 +1,5 @@ +{:deps {org.clojure/clojure {:mvn/version "1.10.3"} + org.bouncycastle/bcprov-jdk15on {:mvn/version "1.70"} + org.bouncycastle/bcpkix-jdk15on {:mvn/version "1.70"} + commons-codec/commons-codec {:mvn/version "1.15"} + com.cemerick/url {:mvn/version "0.1.1"}}} \ No newline at end of file diff --git a/project.clj b/project.clj index 94883c8..c7f8a8b 100644 --- a/project.clj +++ b/project.clj @@ -1,11 +1,12 @@ (defproject clj-oauth "1.5.6-SNAPSHOT" :url "https://github.com/drone-rites/clj-oauth" - :license {:name "Simplified BSD License" - :url "https://opensource.org/licenses/BSD-2-Clause" + :license {:name "Simplified BSD License" + :url "https://opensource.org/licenses/BSD-2-Clause" :distribution :repo} :description "OAuth support for Clojure" :dependencies [[org.clojure/clojure "1.10.3"] [commons-codec/commons-codec "1.15"] [org.bouncycastle/bcprov-jdk15on "1.70"] [org.bouncycastle/bcpkix-jdk15on "1.70"] - [clj-http "3.12.3"]]) + [clj-http "3.12.3"] + [com.cemerick/url "0.1.1"]]) diff --git a/src/oauth/client.clj b/src/oauth/client.clj index d17e0f0..fb95c40 100644 --- a/src/oauth/client.clj +++ b/src/oauth/client.clj @@ -2,9 +2,9 @@ #^{:author "Matt Revelle" :doc "OAuth client library for Clojure."} oauth.client - (:require [oauth.digest :as digest] - [oauth.signature :as sig] - [clj-http.client :as httpclient]) + (:require [oauth.signature :as sig] + [clj-http.client :as httpclient] + [cemerick.url :refer [url]]) (:use [clojure.string :only [join split upper-case]])) (defrecord #^{:doc "OAuth consumer"} @@ -105,6 +105,18 @@ Authorization HTTP header or added as query parameters to the request." oauth-params (assoc unsigned-oauth-params :oauth_signature signature)] (build-request oauth-params extra-params)))) +(defn override-host [original-url host] + (when original-url + (-> original-url + url + (assoc :host host) + str))) + +(defn override-uri [consumer uri-key] + (if-let [host (:override-host consumer)] + (override-host (get consumer uri-key) host) + (get consumer uri-key))) + (defn request-token "Fetch request token for the consumer." ([consumer] @@ -116,7 +128,7 @@ Authorization HTTP header or added as query parameters to the request." (sig/rand-str 30) (sig/msecs->secs (System/currentTimeMillis))) (assoc :oauth_callback callback-uri))] - (post-request-body-decoded (:request-uri consumer) + (post-request-body-decoded (override-uri consumer :request-uri) (build-oauth-token-request consumer (:request-uri consumer) unsigned-params @@ -141,7 +153,7 @@ Authorization HTTP header or added as query parameters to the request." (:oauth_token request-token))) token-secret (:oauth_token_secret request-token)] - (post-request-body-decoded (:access-uri consumer) + (post-request-body-decoded (override-uri consumer :access-uri) (build-oauth-token-request consumer (:access-uri consumer) unsigned-oauth-params @@ -183,7 +195,7 @@ Authorization HTTP header or added as query parameters to the request." (:oauth_token expired-token))) unsigned-oauth-params (assoc base-oauth-params :oauth_session_handle (:oauth_session_handle expired-token))] - (post-request-body-decoded (:access-uri consumer) + (post-request-body-decoded (override-uri consumer :access-uri) (build-oauth-token-request consumer (:access-uri consumer) unsigned-oauth-params @@ -193,7 +205,7 @@ Authorization HTTP header or added as query parameters to the request." (defn xauth-access-token "Request an access token with a username and password with xAuth." [consumer username password] - (post-request-body-decoded (:access-uri consumer) + (post-request-body-decoded (override-uri consumer :access-uri) (build-xauth-access-token-request consumer username password diff --git a/src/oauth/signature.clj b/src/oauth/signature.clj index e1d4358..f2b80f5 100644 --- a/src/oauth/signature.clj +++ b/src/oauth/signature.clj @@ -21,12 +21,12 @@ (name a) (str a))) -(def secure-random (java.security.SecureRandom/getInstance "SHA1PRNG")) +(def secure-random (delay (java.security.SecureRandom/getInstance "SHA1PRNG"))) (defn rand-str "Random string for OAuth requests." [length] - (. (new BigInteger (int (* 5 length)) ^java.util.Random secure-random) toString 32)) + (. (new BigInteger (int (* 5 length)) ^java.util.Random @secure-random) toString 32)) (defn msecs->secs "Convert milliseconds to seconds." @@ -75,9 +75,9 @@ [c base-string & [token-secret]] (str (url-encode (:secret c)) "&" (url-encode (or token-secret "")))) -(def ^:private pem-converter - (doto (JcaPEMKeyConverter.) - (.setProvider "BC"))) +(def ^:private pem-converter (delay + (doto (JcaPEMKeyConverter.) + (.setProvider "BC")))) (defmethod sign :rsa-sha1 [c ^String base-string & [token-secret]] @@ -87,7 +87,7 @@ java.io.StringReader. org.bouncycastle.openssl.PEMParser. .readObject) - private-key (-> ^JcaPEMKeyConverter pem-converter + private-key (-> ^JcaPEMKeyConverter @pem-converter (.getKeyPair key-pair) .getPrivate) signer (doto (java.security.Signature/getInstance "SHA1withRSA" "BC") diff --git a/test/oauth/client_test.clj b/test/oauth/client_test.clj index 1453e21..0de3fd6 100644 --- a/test/oauth/client_test.clj +++ b/test/oauth/client_test.clj @@ -2,11 +2,11 @@ (:require [oauth.client :as oc] [oauth.signature :as sig] :reload-all) - (:use clojure.test - [clojure.pprint :only [pprint]])) + (:use [clojure.pprint :only [pprint]] + [clojure.test])) (deftest ^{:doc "Test creation of authorization header for request_token access."} - request-access-authorization-header + request-access-authorization-header (let [c (oc/make-consumer "GDdmIQH6jhtmLUypg82g" "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" "https://api.twitter.com/oauth/request_token" @@ -15,10 +15,10 @@ :hmac-sha1) ;; Ensure that the params from Twitter example are used. unsigned-params (merge (sig/oauth-params c "QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk" 1272323042) - {:oauth_callback "http://localhost:3005/the_dance/process_callback?service_provider_id=11" - :oauth_consumer_key "GDdmIQH6jhtmLUypg82g" + {:oauth_callback "http://localhost:3005/the_dance/process_callback?service_provider_id=11" + :oauth_consumer_key "GDdmIQH6jhtmLUypg82g" :oauth_signature_method "HMAC-SHA1" - :oauth_version "1.0"}) + :oauth_version "1.0"}) signature (sig/sign c (sig/base-string "POST" (:request-uri c) unsigned-params)) @@ -29,7 +29,7 @@ "OAuth oauth_callback=\"http%3A%2F%2Flocalhost%3A3005%2Fthe_dance%2Fprocess_callback%3Fservice_provider_id%3D11\", oauth_consumer_key=\"GDdmIQH6jhtmLUypg82g\", oauth_nonce=\"QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk\", oauth_signature=\"8wUi7m5HFQy76nowoCThusfgB%2BQ%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1272323042\", oauth_version=\"1.0\"")))) (deftest ^{:doc "Test creation of authorization header for access_token request"} - access-token-authorization-header + access-token-authorization-header (let [c (oc/make-consumer "GDdmIQH6jhtmLUypg82g" "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" "https://api.twitter.com/oauth/request_token" @@ -38,23 +38,23 @@ :hmac-sha1) ;; Ensure that the params from Twitter example are used. unsigned-params (merge (sig/oauth-params c "9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8" 1272323047) - {:oauth_consumer_key "GDdmIQH6jhtmLUypg82g" + {:oauth_consumer_key "GDdmIQH6jhtmLUypg82g" :oauth_signature_method "HMAC-SHA1" - :oauth_token "8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc" - :oauth_verifier "pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY" - :oauth_version "1.0"}) + :oauth_token "8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc" + :oauth_verifier "pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY" + :oauth_version "1.0"}) signature (sig/sign c (sig/base-string "POST" (:access-uri c) unsigned-params) - "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA") ;; token secret + "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA") ;; token secret params (assoc unsigned-params :oauth_signature signature)] (is (= (oc/authorization-header (sort params)) "OAuth oauth_consumer_key=\"GDdmIQH6jhtmLUypg82g\", oauth_nonce=\"9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8\", oauth_signature=\"PUw%2FdHA4fnlJYM6RhXk5IU%2F0fCc%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1272323047\", oauth_token=\"8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc\", oauth_verifier=\"pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY\", oauth_version=\"1.0\"")))) (deftest ^{:doc "Test creation of approval URL"} - user-approval-uri + user-approval-uri (let [c (oc/make-consumer "GDdmIQH6jhtmLUypg82g" "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" "https://api.twitter.com/oauth/request_token" @@ -69,7 +69,7 @@ (oc/user-approval-uri c t {:extra "foo"}))))) (deftest ^{:doc "Test creation of authorization header for refresh access_token request"} - refresh-token-authorization-header + refresh-token-authorization-header (let [c (oc/make-consumer "GDdmIQH6jhtmLUypg82g" "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" "https://api.twitter.com/oauth/request_token" @@ -77,12 +77,12 @@ "https://api.twitter.com/oauth/authorize" :hmac-sha1) unsigned-params (merge (sig/oauth-params c "9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8" 1272323047) - {:oauth_consumer_key "GDdmIQH6jhtmLUypg82g" - :oauth_nonce "9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8" + {:oauth_consumer_key "GDdmIQH6jhtmLUypg82g" + :oauth_nonce "9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8" :oauth_signature_method "HMAC-SHA1" - :oauth_token "8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc" - :oauth_timestamp "1272323047" - :oauth_version "1.0"} + :oauth_token "8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc" + :oauth_timestamp "1272323047" + :oauth_version "1.0"} {:oauth_session_handle "5a10ddsqoqo2rfi"}) signature (sig/sign c (sig/base-string "POST" (:request-uri c) @@ -91,3 +91,37 @@ :oauth_signature signature)] (is (= (oc/authorization-header (sort params)) "OAuth oauth_consumer_key=\"GDdmIQH6jhtmLUypg82g\", oauth_nonce=\"9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8\", oauth_session_handle=\"5a10ddsqoqo2rfi\", oauth_signature=\"f15S84zVZ96f9PwAJrBHq28KIF4%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1272323047\", oauth_token=\"8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc\", oauth_version=\"1.0\"")))) + +(deftest override-host-test + (testing "override host" + (is (= (oc/override-host "https://magento2.mgt/index.php/oauth/token/request" "1234-180-123-89-218.eu.ngrok.io") + "https://1234-180-123-89-218.eu.ngrok.io/index.php/oauth/token/request")))) + +(deftest override-uri-test + (testing "override uri" + (let [consumer (oc/make-consumer "GDdmIQH6jhtmLUypg82g" + "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + "https://api.twitter.com/oauth/request_token" + "https://api.twitter.com/oauth/access_token" + "https://api.twitter.com/oauth/authorize" + :hmac-sha1)] + (testing "no host override" + (is (= (oc/override-uri consumer :request-uri) + "https://api.twitter.com/oauth/request_token"))) + + (testing "request-uri with override" + (is (= (oc/override-uri (assoc consumer :override-host "1234-180-123-89-218.eu.ngrok.io") :request-uri) + "https://1234-180-123-89-218.eu.ngrok.io/oauth/request_token"))) + + (testing "access-uri with override" + (is (= (oc/override-uri (assoc consumer :override-host "1234-180-123-89-218.eu.ngrok.io") :access-uri) + "https://1234-180-123-89-218.eu.ngrok.io/oauth/access_token"))) + + (testing "authorize with override" + (is (= (oc/override-uri (assoc consumer :override-host "1234-180-123-89-218.eu.ngrok.io") :authorize-uri) + "https://1234-180-123-89-218.eu.ngrok.io/oauth/authorize"))) + + (testing "nil URI with override" + (is (nil? (oc/override-uri (-> consumer + (assoc :override-host "1234-180-123-89-218.eu.ngrok.io" + :authorize-uri nil)) :authorize-uri)))))))