Skip to content

Commit

Permalink
Factored session dependency out of Ring middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
Pat Patterson committed Mar 12, 2012
1 parent c839d09 commit 07c6a57
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 56 deletions.
10 changes: 8 additions & 2 deletions README.md
Expand Up @@ -40,8 +40,8 @@ clj-oauth2 wraps clj-http for accessing protected resources.

## Ring Middleware

(:use [clj-oauth2.ring :only [wrap-oauth2]])
(:require [clj-oauth2.client :as oauth2])
(:require [clj-oauth2.client :as oauth2]
[clj-oauth2.ring :as oauth2-ring])

(def login-uri
(get (System/getenv) "LOGIN_URI" "https://login.salesforce.com"))
Expand All @@ -55,6 +55,12 @@ clj-oauth2 wraps clj-http for accessing protected resources.
:scope ["id" "api" "refresh_token"]
:grant-type "authorization_code"
:trace-messages (Boolean/valueOf (get (System/getenv) "DEBUG" "false"))
:get-state oauth2-ring/get-state-from-session
:put-state oauth2-ring/put-state-in-session
:get-target oauth2-ring/get-target-from-session
:put-target oauth2-ring/put-target-in-session
:get-oauth2-data oauth2-ring/get-oauth2-data-from-session
:put-oauth2-data oauth2-ring/put-oauth2-data-in-session
:exclude #"^/public.*"})

; This is the mapping of URL paths to actions
Expand Down
119 changes: 65 additions & 54 deletions src/clj_oauth2/ring.clj
Expand Up @@ -7,8 +7,8 @@
(let [ascii-codes (concat (range 48 58) (range 65 91) (range 97 123))]
(apply str (repeatedly length #(char (rand-nth ascii-codes))))))

(defn- excluded? [uri oauth-params]
(let [exclusion (:exclude oauth-params)]
(defn- excluded? [uri oauth2-params]
(let [exclusion (:exclude oauth2-params)]
(cond
(coll? exclusion)
(some = exclusion uri)
Expand All @@ -19,60 +19,71 @@
(instance? java.util.regex.Pattern exclusion)
(re-matches exclusion uri))))

;; Functions to store state, target URL, OAuth2 data in session
;; requires ring.middleware.session/wrap-session
(defn get-state-from-session [request]
(:state (:session request)))

(defn put-state-in-session [response state]
(assoc response :session (merge (response :session) {:state state})))

(defn get-target-from-session [request]
(:target (:session request)))

(defn put-target-in-session [response target]
(assoc response :session (merge (response :session) {:target target})))

(defn get-oauth2-data-from-session [request]
(:oauth2 (:session request)))

(defn put-oauth2-data-in-session [request response oauth2-data]
(assoc
response
:session (merge
(or (:session response) (:session request))
(or (find response :oauth2) {:oauth2 oauth2-data}))))

;; This Ring wrapper acts as a filter, ensuring that the user has an OAuth
;; token for all but a set of explicitly excluded URLs. The response from
;; oauth2/get-access-token is stored in the session under the :oauth2 key
;; and exposed in the request via the same :oauth2 key.
;; Requires ring.middleware.params/wrap-params
;; ring.middleware.keyword-params/wrap-keyword-params,
;; and ring.middleware.session/wrap-session to have been called first.
;; oauth2/get-access-token is exposed in the request via the :oauth2 key.
;; Requires ring.middleware.params/wrap-params and
;; ring.middleware.keyword-params/wrap-keyword-params to have been called
;; first.
(defn wrap-oauth2
[handler oauth-params]
[handler oauth2-params]
(fn [request]
(if (excluded? (:uri request) oauth-params)
(if (excluded? (:uri request) oauth2-params)
(handler request)
(let [req-session (:session request)]
;; Is the request uri the same as the redirect URI?
;; Use string compare, since java.net.URL.equals resolves hostnames - very slow!
(if (= (.toString (java.net.URL. (name (:scheme request)) (:server-name request) (:server-port request) (:uri request)))
(:redirect-uri oauth-params))
;; We should have an authorization code - get the access token,
;; put it in the session and redirect to the originally requested
;; URL
(let [xsrf-protection (:xsrf-protection req-session)
auth-req (oauth2/make-auth-request oauth-params xsrf-protection)]
{:status 302
:headers {"Location" (:url req-session)}
:session (let [auth-req (oauth2/make-auth-request oauth-params xsrf-protection)
oauth-response (oauth2/get-access-token oauth-params (:params request) auth-req)]
{:oauth2 (merge oauth-response {:trace-messages (:trace-messages oauth-params)})})})
;; We're not handling the callback
(let [oauth-response (:oauth2 req-session)]
(if (nil? oauth-response)
;; No oauth data in session
(let [xsrf-protection (or (:xsrf-protection req-session) (random-string 20))
auth-req (oauth2/make-auth-request oauth-params xsrf-protection)]
;; Redirect to OAuth authentication/authorization
{:status 302
:headers {"Location" (:uri auth-req)}
:session {:xsrf-protection xsrf-protection
:url (str (:uri request) (if (:query-string request) (str "?" (:query-string request))))}})
;; Put the OAuth response on the request and invoke handler
(if-let [response (handler (assoc request :oauth2 oauth-response))]
(if-let [rsp-session (:session response)]
;; Handler has put data in the session
(if-let [new-oauth (find response :oauth2)]
;; Handler has set oauth - merge it all together
(assoc response :session (merge rsp-session new-oauth))
;; Add our oauth data to the session
(assoc response :session (merge rsp-session {:oauth2 oauth-response})))
;; Handler has not modified session
(if-let [new-oauth (find response :oauth2)]
;; Handler has set oauth - merge the new oauth data
;; into the session from the request
(assoc response :session (merge req-session {:oauth2 (:oauth2 response)}))
;; No change to session - our oauth data will
;; be fine. If we were to set it here, we would
;; wipe out the existing session state!
response))))))))))

;; Is the request uri the same as the redirect URI?
;; Use string compare, since java.net.URL.equals resolves hostnames - very slow!
(if (= (.toString
(java.net.URL.
(name (:scheme request))
(:server-name request)
(:server-port request)
(:uri request)))
(:redirect-uri oauth2-params))
;; We should have an authorization code - get the access token, put
;; it in the response and redirect to the originally requested URL
(let [response {:status 302
:headers {"Location" ((:get-target oauth2-params) request)}}
oauth2-data (oauth2/get-access-token
oauth2-params
(:params request)
(oauth2/make-auth-request
oauth2-params
((:get-state oauth2-params) request)))]
((:put-oauth2-data oauth2-params) request response oauth2-data))
;; We're not handling the callback
(let [oauth2-data ((:get-oauth2-data oauth2-params) request)]
(if (nil? oauth2-data)
(let [xsrf-protection (or ((:get-state oauth2-params) request) (random-string 20))
auth-req (oauth2/make-auth-request oauth2-params xsrf-protection)
target (str (:uri request) (if (:query-string request) (str "?" (:query-string request))))
;; Redirect to OAuth 2.0 authentication/authorization
response {:status 302
:headers {"Location" (:uri auth-req)}}]
((:put-target oauth2-params) ((:put-state oauth2-params) response xsrf-protection) target))
;; We have oauth2 data - invoke the handler
(if-let [response (handler (assoc request :oauth2 oauth2-data))]
((:put-oauth2-data oauth2-params) request response oauth2-data))))))))

0 comments on commit 07c6a57

Please sign in to comment.