/
ring.clj
93 lines (83 loc) · 4.22 KB
/
ring.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
(ns clj-oauth2.ring
(:require [clojure.contrib.java-utils :as java-utils]
[clj-oauth2.client :as oauth2]))
;; Random mixed case alphanumeric
(defn- random-string [length]
(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 oauth2-params]
(let [exclusion (:exclude oauth2-params)]
(cond
(coll? exclusion)
(some = exclusion uri)
(string? exclusion)
(= exclusion uri)
(fn? exclusion)
(exclusion uri)
(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}))))
(defn request-uri [request oauth2-params]
(let [scheme (if (:force-https oauth2-params) "https" (name (:scheme request)))
port (if (or (and (= (name (:scheme request)) "http")
(not= (:server-port request) 80))
(and (= (name (:scheme request)) "https")
(not= (:server-port request) 443)))
(str ":" (:server-port request)))]
(str scheme "://" (:server-name request) port (:uri request))))
;; 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 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 oauth2-params]
(fn [request]
(if (excluded? (:uri request) oauth2-params)
(handler request)
;; Is the request uri the same as the redirect URI?
;; Use string compare, since java.net.URL.equals resolves hostnames - very slow!
(if (= (request-uri request oauth2-params)
(.toString (java.net.URL. (: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))))))))