Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit e318923fdd2dade93ce1cee34a3d23356bcf0f6c Matt Revelle committed with Raynes Jul 20, 2009
@@ -0,0 +1,37 @@
+# OAuth client support for Clojure #
+
+`clj-oauth` provides [OAuth](http://oauth.net) client support for Clojure programs.
+
+
+# Example #
+
+ (require ['oauth.client :as 'oc])
+
+ (def consumer (oc/make-consumer <consumer-token>
+ <consumer-token-secret>
+ "http://twitter.com/oauth/request_token"
+ "http://twitter.com/oauth/access_token"
+ "http://twitter.com/oauth/authorize"
+ :hmac-sha1))
+
+ ;; Fetch request token
+ (def request-token (:oauth_token (oc/request-token consumer)))
+
+ ;; Redirect the OAuth User to this URI for authorization
+ (def approval-uri (oc/user-approval-uri consumer
+ request-token)
+
+ ;; Assuming the User has approved the request token, trade it for an access token
+ (def access-token (:oauth_token (oc/access-token consumer
+ request-token)))
+
+ ;; The access token may be used with our other Consumer credentials to authorize requests
+ (def credentials (oc/sign-request consumer
+ access-token
+ :POST
+ "http://twitter.com/statuses/update.json"
+ <update-params>))
+
+ (http/post "http://twitter.com/statuses/update.json"
+ :query (merge credentials <update-params>)))
+
@@ -0,0 +1,70 @@
+<project name="clj-oauth" default="deploy">
+
+ <!-- Override these with -Dclojure.jar=... in your Ant invocation. -->
+ <property name="clojure.jar" location="/opt/clojure/clojure.jar"/>
+ <property name="clojure.contrib.jar" location="/opt/clojure-contrib/clojure-contrib.jar"/>
+
+ <available property="hasclojure" file="${clojure.jar}"/>
+
+ <!-- Library. -->
+ <property name="src" location="src"/>
+ <property name="lib" location="lib"/>
+ <property name="build" location="classes"/>
+ <property name="jarfile" location="${ant.project.name}.jar"/>
+ <property name="deploy" location="deploy"/>
+
+ <target name="init">
+ <tstamp/>
+ <mkdir dir="${build}"/>
+ <mkdir dir="${deploy}"/>
+ </target>
+
+ <target name="clean" description="Remove generated files and directories.">
+ <delete dir="${deploy}"/>
+ <delete dir="${build}"/>
+ <delete file="${jarfile}"/>
+ </target>
+
+ <target name="compile_clojure" depends="init"
+ description="Compile Clojure sources."
+ if="hasclojure">
+ <java classname="clojure.lang.Compile">
+ <classpath>
+ <path location="${src}"/>
+ <path location="${build}"/>
+
+ <path location="${lib}/clj-apache-http.jar"/>
+ <path location="${lib}/commons-logging-1.1.1.jar"/>
+ <path location="${lib}/httpclient-4.0-beta2.jar"/>
+ <path location="${lib}/httpcore-4.0-beta3.jar"/>
+ <path location="${lib}/httpmime-4.0-beta2.jar"/>
+
+ <path location="${lib}/commons-codec-1.3.jar"/>
+
+ <path location="${clojure.jar}"/>
+ <path location="${clojure.contrib.jar}"/>
+ </classpath>
+
+ <sysproperty key="clojure.compile.path" value="${build}"/>
+ <arg value="oauth"/>
+ </java>
+ </target>
+
+ <target name="jar" description="Create jar file." depends="compile_clojure">
+ <jar jarfile="${jarfile}">
+ <!-- <fileset dir="${src}" includes="**/*.clj"/> -->
+ <fileset dir="${build}" includes="**/*.class"/>
+ <manifest>
+ <attribute name="Class-Path" value="."/>
+ </manifest>
+ </jar>
+ </target>
+
+ <target name="deploy" description="Copy appropriate jar files to one place."
+ depends="jar">
+ <copy todir="${deploy}" verbose="true" file="${jarfile}"/>
+ <copy todir="${deploy}" verbose="true">
+ <fileset dir="${lib}" includes="*.jar"/>
+ </copy>
+ </target>
+</project>
@@ -0,0 +1,2 @@
+(ns oauth
+ (:require oauth.client))
@@ -0,0 +1 @@
+(ns oauth)
@@ -0,0 +1,28 @@
+(ns oath.base64
+ #^{:doc "Base64 encoding code from Cubix's comment at:
+http://www.fatvat.co.uk/2009/02/bit-shifting-in-clojure.html"})
+
+(def *encode-table* "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
+
+(defn encode-num
+ [num]
+ (let [a (bit-and num 63)
+ b (bit-shift-right (bit-and num 4032) 6)
+ c (bit-shift-right (bit-and num 258048) 12)
+ d (bit-shift-right (bit-and num 16515072) 18)]
+ (map (fn [x] (nth *encode-table* x )) (list d c b a))))
+
+(defn encode
+ "Lazily encode a sequence as base64"
+ [s]
+ (if s
+ (let [x (map int (take 3 s))]
+ (if (= 3 (count x))
+ (let [num (+ (nth x 2) (* 256 (nth x 1)) (* 256 256 (first x)))]
+ (lazy-cat (encode-num num) (encode (drop 3 s))))
+ (padding x)))))
+
+(defn padding [ints]
+ (let [ints-zero-pad (take 2 (concat ints '(0)))]
+ (let [num (+ (* 256 256 (nth ints-zero-pad 0)) (* 256 (nth ints-zero-pad 1)))]
+ (take 4 (concat (take (+ (count ints) 1) (encode-num num)) (repeat \=))))))
@@ -0,0 +1,141 @@
+(ns
+ #^{:author "Matt Revelle"
+ :doc "OAuth client library for Clojure."}
+ oauth.client
+ (:require [oauth.digest :as digest]
+ [com.twinql.clojure.http :as http])
+ (:use [clojure.contrib.str-utils :only [str-join re-split]]
+ [clojure.contrib.str-utils2 :only [upper-case]]
+ [clojure.contrib.java-utils :only [as-str]]))
+
+(declare rand-str
+ base-string
+ sign
+ url-encode
+ oauth-params)
+
+(defstruct #^{: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
+ key
+ secret
+ request-uri
+ access-uri
+ authorize-uri
+ signature-method))
+
+(defn request-token
+ "Fetch request token for the consumer."
+ [consumer]
+ (let [unsigned-params (oauth-params consumer)
+ params (assoc unsigned-params :oauth_signature (sign consumer
+ (base-string "POST"
+ (:request-uri consumer)
+ unsigned-params)))
+ response (http/post (:request-uri consumer)
+ :query params
+ :parameters (http/map->params {:use-expect-continue false})
+ :as :string)]
+ (into {} (map (fn [kv]
+ (let [[k v] (re-split #"=" kv)]
+ [(keyword k) v]))
+ (re-split #"&" (:content response))))))
+
+(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 callback-uri]
+ (.toString (http/resolve-uri (:authorize-uri consumer)
+ {:oauth_token token
+ :oauth_callback callback-uri})))
+
+(defn access-token
+ "Exchange a request token for an access token."
+ [consumer request-token]
+ (let [unsigned-params (oauth-params consumer request-token)
+ params (assoc unsigned-params
+ :oauth_signature (sign consumer
+ (base-string "POST"
+ (:access-uri consumer)
+ unsigned-params)))
+ response (http/post (:access-uri consumer)
+ :query params
+ :parameters (http/map->params {:use-expect-continue false})
+ :as :string)]
+ (println (:content response))
+ (into {} (map (fn [kv]
+ (let [[k v] (re-split #"=" kv)]
+ [(keyword k) v]))
+ (re-split #"&" (:content response))))))
+
+(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 request-method request-uri & [request-params]]
+ (let [unsigned-oauth-params (oauth-params consumer token)
+ unsigned-params (merge request-params
+ unsigned-oauth-params)]
+ (assoc unsigned-oauth-params :oauth_signature (sign consumer
+ (base-string (-> request-method
+ as-str
+ upper-case)
+ request-uri
+ unsigned-params)))))
+
+(defn authorization-header
+ "OAuth credentials formatted for the Authorization HTTP header."
+ [realm credentials]
+ (str "OAuth " (str-join "\n" (map (fn [[k v]]
+ (str (as-str k) "=\"" v "\""))
+ (assoc credentials :realm realm)))))
+
+(defn rand-str
+ "Random string for OAuth requests."
+ [length]
+ (let [valid-chars (map char (concat (range 48 58)
+ (range 97 123)))
+ rand-char #(nth valid-chars (rand (count valid-chars)))]
+ (apply str (take length (repeatedly rand-char)))))
+
+(defn base-string
+ [method base-url params]
+ (str-join "&" [method
+ (url-encode base-url)
+ (url-encode (str-join "&" (map (fn [[k v]]
+ (str (name k) "=" v))
+ (sort params))))]))
+
+(defmulti sign
+ "Sign a base string for authentication."
+ (fn [c & r] (:signature-method c)))
+
+(defmethod sign :hmac-sha1
+ [c base-string & [token-secret]]
+ (let [key (str (:secret c) "&" (or token-secret ""))]
+ (digest/hmac key base-string)))
+
+(defn url-encode
+ [s]
+ (java.net.URLEncoder/encode s "UTF-8"))
+
+(defn oauth-params
+ "Build a map of parameters needed for OAuth requests."
+ ([consumer]
+ {:oauth_consumer_key (:key consumer)
+ :oauth_signature_method "HMAC-SHA1"
+ :oauth_timestamp (System/currentTimeMillis)
+ :oauth_nonce (rand-str 30)
+ :oauth_version "1.0"})
+ ([consumer token]
+ (assoc (oauth-params consumer)
+ :oauth_token token)))
@@ -0,0 +1,41 @@
+(ns
+ #^{:author "Matt Revelle"
+ :doc "OAuth library for Clojure"}
+ oauth.client
+ (:require [com.twinql.clojure.http :as http]))
+
+(defstruct #^{:doc "OAuth consumer"} consumer
+ :key
+ :secret
+ :site)
+
+(defn make-consumer
+ "Make a consumer struct map."
+ [key secret site]
+ (struct consumer
+ key
+ secret))
+
+(defn request-token
+ "Fetch request token for the consumer."
+ [consumer]
+ (let [response (http/get (request-uri (:site consumer))
+ :as :string)]
+ response))
+
+(defn access-token
+ "Exchange a request token for an access token."
+ [request-token access-uri]
+ )
+
+(defn request-uri
+ [site]
+ (str site "/oauth/request_token"))
+
+(defn access-uri
+ [site]
+ (str site "/oauth/access_token"))
+
+(defn authorize-uri
+ [site]
+ (str site "/oauth/authorize"))
@@ -0,0 +1,12 @@
+(ns oath.coding
+ (:import (javax.crypto Mac)
+ (javax.crypto.spec SecretKeySpec)))
+
+(defn hmac
+ "Calculate HMAC signature for given data."
+ [key data]
+ (let [hmac-sha1 "HmacSHA1"
+ signing-key (SecretKeySpec. (.getBytes key) hmac-sha1)
+ mac (doto (Mac. hmac-sha1)
+ (.init signing-key))]
+ ))
@@ -0,0 +1,13 @@
+(ns oauth.digest
+ (:import (javax.crypto Mac)
+ (javax.crypto.spec SecretKeySpec)))
+
+(defn hmac
+ "Calculate HMAC signature for given data."
+ [key data]
+ (let [hmac-sha1 "HmacSHA1"
+ signing-key (SecretKeySpec. (.getBytes key) hmac-sha1)
+ mac (doto (Mac/getInstance hmac-sha1) (.init signing-key))]
+ (String. (org.apache.commons.codec.binary.Base64/encodeBase64
+ (.doFinal mac (.getBytes data)))
+ "UTF-8")))
@@ -0,0 +1,13 @@
+(ns oath.digest
+ (:import (javax.crypto Mac)
+ (javax.crypto.spec SecretKeySpec)))
+
+(defn hmac
+ "Calculate HMAC signature for given data."
+ [key data]
+ (let [hmac-sha1 "HmacSHA1"
+ signing-key (SecretKeySpec. (.getBytes key) hmac-sha1)
+ mac (doto (Mac. hmac-sha1)
+ (.init signing-key))]
+ (base64-encode (.doFinal mac (.getBytes data)))))
+
@@ -0,0 +1,4 @@
+(ns oath.hmac
+ (:import (javax.crypto Mac)
+ (javax.crypto.spec SecretKeySpec)))
+
@@ -0,0 +1,2 @@
+(ns tests
+ (:use clojure.test))
Oops, something went wrong.

0 comments on commit e318923

Please sign in to comment.