forked from weavejester/ring-oauth2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoauth2.clj
129 lines (107 loc) · 4.86 KB
/
oauth2.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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
(ns ring.middleware.oauth2
(:require [clj-http.client :as http]
[clj-time.core :as time]
[clojure.string :as str]
[crypto.random :as random]
[ring.util.codec :as codec]
[ring.util.request :as req]
[ring.util.response :as resp]))
(defn- redirect-uri [profile request]
(-> (req/request-url request)
(java.net.URI/create)
(.resolve (:redirect-uri profile))
str))
(defn- scopes [profile]
(str/join " " (map name (:scopes profile))))
(defn- authorize-uri [profile request state]
(str (:authorize-uri profile)
(if (.contains ^String (:authorize-uri profile) "?") "&" "?")
(codec/form-encode {:response_type "code"
:client_id (:client-id profile)
:redirect_uri (redirect-uri profile request)
:scope (scopes profile)
:state state})))
(defn- random-state []
(-> (random/base64 9) (str/replace "+" "-") (str/replace "/" "_")))
(defn- make-launch-handler [profile]
(fn [{:keys [session] :or {session {}} :as request}]
(let [state (random-state)]
(-> (resp/redirect (authorize-uri profile request state))
(assoc :session (assoc session ::state state))))))
(defn- state-matches? [request]
(= (get-in request [:session ::state])
(get-in request [:query-params "state"])))
(defn- coerce-to-int [n]
(if (string? n)
(Integer/parseInt n)
n))
(defn- format-access-token
[{{:keys [access_token expires_in refresh_token id_token]} :body :as r}]
(-> {:token access_token}
(cond-> expires_in (assoc :expires (-> expires_in
coerce-to-int
time/seconds
time/from-now))
refresh_token (assoc :refresh-token refresh_token)
id_token (assoc :id-token id_token))))
(defn- get-authorization-code [request]
(get-in request [:query-params "code"]))
(defn- request-params [profile request]
{:grant_type "authorization_code"
:code (get-authorization-code request)
:redirect_uri (redirect-uri profile request)})
(defn- add-header-credentials [opts id secret]
(assoc opts :basic-auth [id secret]))
(defn- add-form-credentials [opts id secret]
(assoc opts :form-params (-> (:form-params opts)
(merge {:client_id id
:client_secret secret}))))
(defn- get-access-token
[{:keys [access-token-uri client-id client-secret basic-auth?]
:or {basic-auth? false} :as profile} request]
(format-access-token
(http/post access-token-uri
(cond-> {:accept :json, :as :json,
:form-params (request-params profile request)}
basic-auth? (add-header-credentials client-id client-secret)
(not basic-auth?) (add-form-credentials client-id client-secret)))))
(defn state-mismatch-handler [_]
{:status 400, :headers {}, :body "State mismatch"})
(defn no-auth-code-handler [_]
{:status 400, :headers {}, :body "No authorization code"})
(defn- make-redirect-handler [{:keys [id landing-uri] :as profile}]
(let [state-mismatch-handler (:state-mismatch-handler
profile state-mismatch-handler)
no-auth-code-handler (:no-auth-code-handler
profile no-auth-code-handler)]
(fn [{:keys [session] :or {session {}} :as request}]
(cond
(not (state-matches? request))
(state-mismatch-handler request)
(nil? (get-authorization-code request))
(no-auth-code-handler request)
:else
(let [access-token (get-access-token profile request)]
(-> (resp/redirect landing-uri)
(assoc :session (-> session
(assoc-in [::access-tokens id] access-token)
(dissoc ::state)))))))))
(defn- assoc-access-tokens [request]
(if-let [tokens (-> request :session ::access-tokens)]
(assoc request :oauth2/access-tokens tokens)
request))
(defn- parse-redirect-url [{:keys [redirect-uri]}]
(.getPath (java.net.URI. redirect-uri)))
(defn- valid-profile? [{:keys [client-id client-secret] :as profile}]
(and (some? client-id) (some? client-secret)))
(defn wrap-oauth2 [handler profiles]
{:pre [(every? valid-profile? (vals profiles))]}
(let [profiles (for [[k v] profiles] (assoc v :id k))
launches (into {} (map (juxt :launch-uri identity)) profiles)
redirects (into {} (map (juxt parse-redirect-url identity)) profiles)]
(fn [{:keys [uri] :as request}]
(if-let [profile (launches uri)]
((make-launch-handler profile) request)
(if-let [profile (redirects uri)]
((make-redirect-handler profile) request)
(handler (assoc-access-tokens request)))))))