Browse code
More refactoring of cl-oid-connect
Various changes to decouple the oid-connect implementation from the
web-server as well as reducing the amount of boilerplate in the various
files.
Showing 3 changed files
... | ... |
@@ -1,37 +1,38 @@ |
1 | 1 |
;;;; cl-oid-connect.lisp |
2 | 2 |
;;;; TODO: Need to refactor out server names!!! |
3 |
+ |
|
3 | 4 |
#| |
4 |
-|Copyright (c) 2015 Edward Langley |
|
5 |
-|All rights reserved. |
|
6 |
-| |
|
7 |
-|Redistribution and use in source and binary forms, with or without |
|
8 |
-|modification, are permitted provided that the following conditions |
|
9 |
-|are met: |
|
10 |
-| |
|
11 |
-|Redistributions of source code must retain the above copyright notice, |
|
12 |
-|this list of conditions and the following disclaimer. |
|
13 |
-| |
|
14 |
-|Redistributions in binary form must reproduce the above copyright |
|
15 |
-|notice, this list of conditions and the following disclaimer in the |
|
16 |
-|documentation and/or other materials provided with the distribution. |
|
17 |
-| |
|
18 |
-|Neither the name of the project's author nor the names of its |
|
19 |
-|contributors may be used to endorse or promote products derived from |
|
20 |
-|this software without specific prior written permission. |
|
21 |
-| |
|
22 |
-|THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
23 |
-|"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
24 |
-|LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
25 |
-|FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
26 |
-|HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
27 |
-|SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED |
|
28 |
-|TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
29 |
-|PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
30 |
-|LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
31 |
-|NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
32 |
-|SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
33 |
-| |
|
34 |
-|# |
|
5 |
+ |Copyright (c) 2015 Edward Langley |
|
6 |
+ |All rights reserved. |
|
7 |
+ | |
|
8 |
+ |Redistribution and use in source and binary forms, with or without |
|
9 |
+ |modification, are permitted provided that the following conditions |
|
10 |
+ |are met: |
|
11 |
+ | |
|
12 |
+ |Redistributions of source code must retain the above copyright notice, |
|
13 |
+ |this list of conditions and the following disclaimer. |
|
14 |
+ | |
|
15 |
+ |Redistributions in binary form must reproduce the above copyright |
|
16 |
+ |notice, this list of conditions and the following disclaimer in the |
|
17 |
+ |documentation and/or other materials provided with the distribution. |
|
18 |
+ | |
|
19 |
+ |Neither the name of the project's author nor the names of its |
|
20 |
+ |contributors may be used to endorse or promote products derived from |
|
21 |
+ |this software without specific prior written permission. |
|
22 |
+ | |
|
23 |
+ |THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
24 |
+ |"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
25 |
+ |LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
26 |
+ |FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
27 |
+ |HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
28 |
+ |SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED |
|
29 |
+ |TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
30 |
+ |PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
31 |
+ |LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
32 |
+ |NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
33 |
+ |SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
34 |
+ | |
|
35 |
+ |# |
|
35 | 36 |
|
36 | 37 |
(in-package :cl-oid-connect) |
37 | 38 |
; Should this be here? |
... | ... |
@@ -60,9 +61,29 @@ |
60 | 61 |
|
61 | 62 |
(defmacro def-route ((url args &key (app *oid*) (method :GET)) &body body) |
62 | 63 |
`(setf (ningle:route ,app ,url :method ,method) |
63 |
- #'(lambda ,args |
|
64 |
- (declare (ignorable ,@args)) |
|
65 |
- ,@body))) |
|
64 |
+ (lambda ,args |
|
65 |
+ (declare (ignorable ,@args)) |
|
66 |
+ ,@body))) |
|
67 |
+ |
|
68 |
+(defun gen-state (len) |
|
69 |
+ (with-output-to-string (stream) |
|
70 |
+ (let ((*print-base* 36)) |
|
71 |
+ (loop repeat len |
|
72 |
+ do (princ (random 36) stream))))) |
|
73 |
+ |
|
74 |
+(defun valid-state (received-state) |
|
75 |
+ (let* ((session (context :session)) |
|
76 |
+ (saved-state (gethash :state session))) |
|
77 |
+ (equal saved-state received-state))) |
|
78 |
+ |
|
79 |
+(defmacro my-with-context-variables ((&rest vars) &body body) |
|
80 |
+ "This improves fukamachi's version by permitting the variable to be stored somewhere |
|
81 |
+ besides the symbol corresponding to the keyword." |
|
82 |
+ `(symbol-macrolet |
|
83 |
+ ,(loop for (var key) in (ensure-mapping vars) |
|
84 |
+ for form = `(context ,(intern (string key) :keyword)) |
|
85 |
+ collect `(,var ,form)) |
|
86 |
+ ,@body)) |
|
66 | 87 |
|
67 | 88 |
(defparameter *oid* (make-instance 'ningle:<app>)) |
68 | 89 |
(setf drakma:*text-content-types* (cons '("application" . "json") drakma:*text-content-types*)) |
... | ... |
@@ -77,44 +98,14 @@ |
77 | 98 |
(userinfo-endpoint nil :accessor t) |
78 | 99 |
(auth-scope "openid profile email" :accessor t) |
79 | 100 |
(redirect-uri nil :accessor t)))) |
101 |
+ |
|
80 | 102 |
(sheeple:defmessage get-user-info (a b)) |
81 | 103 |
(sheeple:defmessage get-access-token (a b)) |
82 | 104 |
|
83 | 105 |
(sheeple:defreply get-user-info ((a =endpoint-schema=) (b sheeple:=string=))) |
84 | 106 |
(sheeple:defreply get-access-token ((a =endpoint-schema=) (b sheeple:=string=))) |
85 | 107 |
|
86 |
-(defparameter *fbook-info* (sheeple:clone =service-info=)) |
|
87 |
-(defparameter *goog-info* (sheeple:clone =service-info=)) |
|
88 | 108 |
(defparameter *endpoint-schema* nil) |
89 |
-(defparameter *goog-endpoint-schema* (defobject (=endpoint-schema= *goog-info*))) |
|
90 |
- |
|
91 |
-(defproto *fbook-endpoint-schema* (=endpoint-schema= *fbook-info*) |
|
92 |
- ((auth-endpoint "https://www.facebook.com/dialog/oauth") |
|
93 |
- (token-endpoint "https://graph.facebook.com/v2.3/oauth/access_token") |
|
94 |
- (userinfo-endpoint "https://graph.facebook.com/v2.3/me") |
|
95 |
- (auth-scope "email") |
|
96 |
- (redirect-uri "http://srv2.elangley.org:9090/oidc_callback/facebook"))) |
|
97 |
- |
|
98 |
-(sheeple:defreply get-access-token ((endpoint-schema *fbook-endpoint-schema*) (code sheeple:=string=)) |
|
99 |
- (cl-json:decode-json-from-string |
|
100 |
- (drakma:http-request (token-endpoint endpoint-schema) |
|
101 |
- :method :post |
|
102 |
- :redirect nil |
|
103 |
- :parameters `(("code" . ,code) |
|
104 |
- ("client_id" . ,(client-id endpoint-schema)) |
|
105 |
- ("app_id" . ,(client-id endpoint-schema)) |
|
106 |
- ("client_secret" . ,(secret endpoint-schema)) |
|
107 |
- ("redirect_uri" . ,(redirect-uri endpoint-schema)) |
|
108 |
- ("grant_type" . "authorization_code") |
|
109 |
- ("") |
|
110 |
- )))) |
|
111 |
- |
|
112 |
-(sheeple:defreply get-user-info ((endpoint-schema *fbook-endpoint-schema*) (access-token sheeple:=string=)) |
|
113 |
- (let ((endpoint (userinfo-endpoint endpoint-schema))) |
|
114 |
- (cl-json:decode-json-from-string |
|
115 |
- (drakma:http-request endpoint |
|
116 |
- :parameters `(("access_token" . ,access-token)))))) |
|
117 |
- |
|
118 | 109 |
(defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal)) |
119 | 110 |
(defmacro assoc-cdr (key alist &optional (test '#'eql)) `(cdr (assoc ,key ,alist :test ,test))) |
120 | 111 |
|
... | ... |
@@ -145,18 +136,6 @@ |
145 | 136 |
("redirect_uri" . ,(redirect-uri endpoint-schema)) |
146 | 137 |
("state" . ,state)))) |
147 | 138 |
|
148 |
-(defun gen-state (len) |
|
149 |
- (with-output-to-string (stream) |
|
150 |
- (let ((*print-base* 36)) |
|
151 |
- (loop repeat len |
|
152 |
- do (princ (random 36) stream))))) |
|
153 |
- |
|
154 |
- |
|
155 |
-(defun valid-state (received-state) |
|
156 |
- (let* ((session (context :session)) |
|
157 |
- (saved-state (gethash :state session))) |
|
158 |
- (equal saved-state received-state))) |
|
159 |
- |
|
160 | 139 |
(defmacro auth-entry-point (name endpoint-schema) |
161 | 140 |
`(defun ,name (params) |
162 | 141 |
(declare (ignore params)) |
... | ... |
@@ -169,8 +148,8 @@ |
169 | 148 |
(if (< rcode 400) `(302 (:location ,(format nil "~a" uri))) |
170 | 149 |
content)))))) |
171 | 150 |
|
172 |
-(flet ((get-code (params) (assoc-cdr "code" params #'equal))) |
|
173 |
- (defun run-callback-function (endpoint-schema params get-login-data get-app-user-cb) |
|
151 |
+(defun run-callback-function (endpoint-schema params get-app-user-cb get-login-data) |
|
152 |
+ (flet ((get-code (params) (assoc-cdr "code" params #'equal))) |
|
174 | 153 |
(let ((a-t (get-access-token endpoint-schema (get-code params)))) |
175 | 154 |
(auth-callback-skeleton params (:endpoint-schema endpoint-schema |
176 | 155 |
:auth-session-vars (accesstoken userinfo idtoken app-user)) |
... | ... |
@@ -181,10 +160,14 @@ |
181 | 160 |
app-user (funcall get-app-user-cb user-info id-token access-token))) |
182 | 161 |
'(302 (:location "/")))))) |
183 | 162 |
|
184 |
-(defmacro def-callback-generator (name generator-args callback-args &body body) |
|
185 |
- `(defun ,name ,generator-args |
|
186 |
- (lambda ,callback-args |
|
187 |
- ,@body))) |
|
163 |
+(defmacro generate-auth-callback (name endpoint-schema params &body body) |
|
164 |
+ (with-gensyms (get-app-user-cb cb-params) |
|
165 |
+ `(defun ,name (,get-app-user-cb) |
|
166 |
+ (lambda (,cb-params) |
|
167 |
+ (run-callback-function |
|
168 |
+ ,endpoint-schema ,cb-params ,get-app-user-cb |
|
169 |
+ (lambda ,params |
|
170 |
+ ,@body)))))) |
|
188 | 171 |
|
189 | 172 |
(defmacro reject-when-state-invalid (params &body body) |
190 | 173 |
(alexandria:with-gensyms (received-state) |
... | ... |
@@ -208,15 +191,6 @@ |
208 | 191 |
|
209 | 192 |
(define-condition user-not-logged-in (error) ()) |
210 | 193 |
|
211 |
-(defmacro my-with-context-variables ((&rest vars) &body body) |
|
212 |
- "This improves fukamachi's version by permitting the variable to be stored somewhere |
|
213 |
- besides the symbol corresponding to the keyword." |
|
214 |
- `(symbol-macrolet |
|
215 |
- ,(loop for (var key) in (ensure-mapping vars) |
|
216 |
- for form = `(context ,(intern (string key) :keyword)) |
|
217 |
- collect `(,var ,form)) |
|
218 |
- ,@body)) |
|
219 |
- |
|
220 | 194 |
(defmacro ensure-logged-in (&body body) |
221 | 195 |
"Ensure that the user is logged in: otherwise throw the condition user-not-logged-in" |
222 | 196 |
(alexandria:with-gensyms (session userinfo) |
... | ... |
@@ -226,13 +200,10 @@ |
226 | 200 |
(error 'user-not-logged-in) |
227 | 201 |
(progn ,@body)))))) |
228 | 202 |
|
229 |
-(flet |
|
230 |
- ((handle-no-user (main-body handler-body) |
|
231 |
- `(handler-case |
|
232 |
- (ensure-logged-in ,@main-body) |
|
233 |
- (user-not-logged-in (e) |
|
234 |
- (declare (ignorable e)) |
|
235 |
- ,@handler-body)))) |
|
203 |
+(flet ((handle-no-user (main-body handler-body) |
|
204 |
+ `(handler-case (ensure-logged-in ,@main-body) |
|
205 |
+ (user-not-logged-in (e) (declare (ignorable e)) |
|
206 |
+ ,@handler-body)))) |
|
236 | 207 |
|
237 | 208 |
(defmacro check-login (&body body) |
238 | 209 |
"Returns an HTTP 401 Error if not logged in." |
... | ... |
@@ -245,21 +216,40 @@ |
245 | 216 |
(setf next-page (lack.request:request-path-info *request*)) |
246 | 217 |
'(302 (:location "/login"))))))) |
247 | 218 |
|
248 |
-(defun load-facebook-info (loadfrom) |
|
249 |
- (with-open-file (fbook-info (truename loadfrom)) |
|
250 |
- (let* ((data (yason:parse fbook-info)) |
|
251 |
- (client-id (gethash "client-id" data)) |
|
252 |
- (secret (gethash "secret" data))) |
|
253 |
- (setf (client-id *FBOOK-INFO*) client-id) |
|
254 |
- (setf (secret *FBOOK-INFO*) secret)))) |
|
255 |
- |
|
256 |
-(defun load-google-info (loadfrom) |
|
257 |
- (with-open-file (goog-info (truename loadfrom)) |
|
258 |
- (let* ((data (yason:parse goog-info)) |
|
259 |
- (client-id (gethash "client-id" data)) |
|
260 |
- (secret (gethash "secret" data))) |
|
261 |
- (setf (client-id *GOOG-INFO*) client-id) |
|
262 |
- (setf (secret *GOOG-INFO*) secret)))) |
|
219 |
+(defparameter *fbook-info* (sheeple:clone =service-info=)) |
|
220 |
+(defparameter *goog-info* (sheeple:clone =service-info=)) |
|
221 |
+(defparameter *goog-endpoint-schema* (defobject (=endpoint-schema= *goog-info*))) |
|
222 |
+ |
|
223 |
+(defproto *fbook-endpoint-schema* (=endpoint-schema= *fbook-info*) |
|
224 |
+ ((auth-endpoint "https://www.facebook.com/dialog/oauth") |
|
225 |
+ (token-endpoint "https://graph.facebook.com/v2.3/oauth/access_token") |
|
226 |
+ (userinfo-endpoint "https://graph.facebook.com/v2.3/me") |
|
227 |
+ (auth-scope "email") |
|
228 |
+ (redirect-uri "http://srv2.elangley.org:9090/oidc_callback/facebook"))) |
|
229 |
+ |
|
230 |
+(sheeple:defreply get-access-token ((endpoint-schema *fbook-endpoint-schema*) (code sheeple:=string=)) |
|
231 |
+ (cl-json:decode-json-from-string |
|
232 |
+ (drakma:http-request (token-endpoint endpoint-schema) |
|
233 |
+ :method :post |
|
234 |
+ :redirect nil |
|
235 |
+ :parameters `(("code" . ,code) |
|
236 |
+ ("client_id" . ,(client-id endpoint-schema)) |
|
237 |
+ ("app_id" . ,(client-id endpoint-schema)) |
|
238 |
+ ("client_secret" . ,(secret endpoint-schema)) |
|
239 |
+ ("redirect_uri" . ,(redirect-uri endpoint-schema)) |
|
240 |
+ ("grant_type" . "authorization_code") |
|
241 |
+ ("") |
|
242 |
+ )))) |
|
243 |
+ |
|
244 |
+(sheeple:defreply get-user-info ((endpoint-schema *fbook-endpoint-schema*) (access-token sheeple:=string=)) |
|
245 |
+ (let ((endpoint (userinfo-endpoint endpoint-schema))) |
|
246 |
+ (cl-json:decode-json-from-string |
|
247 |
+ (drakma:http-request endpoint |
|
248 |
+ :parameters `(("access_token" . ,access-token)))))) |
|
249 |
+ |
|
250 |
+(defun load-provider-secrets (provider-info secrets) |
|
251 |
+ (setf (client-id provider-info) (assoc-cdr :client-id secrets) |
|
252 |
+ (secret provider-info) (assoc-cdr :secret secrets))) |
|
263 | 253 |
|
264 | 254 |
(defun goog-get-access-token (endpoint-schema code) |
265 | 255 |
(cl-json:decode-json-from-string |
... | ... |
@@ -289,49 +279,35 @@ |
289 | 279 |
(auth-entry-point google-login-entry *goog-endpoint-schema*) |
290 | 280 |
(auth-entry-point facebook-login-entry *fbook-endpoint-schema*) |
291 | 281 |
|
292 |
-(labels ((get-real-access-token (a-t) (assoc-cdr :access--token a-t)) |
|
293 |
- (get-id-token (a-t) (cljwt:decode (assoc-cdr :id--token a-t) :fail-if-unsupported nil)) |
|
294 |
- (get-login-data (a-t) |
|
295 |
- (let ((access-token (get-real-access-token a-t))) |
|
296 |
- (values access-token |
|
297 |
- (get-user-info *goog-endpoint-schema* access-token) |
|
298 |
- (get-id-token a-t))))) |
|
299 |
- |
|
300 |
- (def-callback-generator google-callback (get-app-user-cb) (params) |
|
301 |
- (run-callback-function *goog-endpoint-schema* params #'get-login-data get-app-user-cb))) |
|
302 |
- |
|
303 |
-(labels ((get-id-token (a-t) (assoc-cdr :access--token a-t)) ; <-- access--token is not a mistake |
|
304 |
- (get-login-data (a-t) |
|
305 |
- (let ((id-token (get-id-token a-t))) |
|
306 |
- (values a-t (get-user-info *fbook-endpoint-schema* id-token) id-token)))) |
|
307 |
- |
|
308 |
- (def-callback-generator facebook-callback (get-app-user-cb) (params) |
|
309 |
- (run-callback-function *fbook-endpoint-schema* params #'get-login-data get-app-user-cb))) |
|
310 |
- |
|
311 |
-(defun userinfo-route (params) |
|
312 |
- (declare (ignore params)) |
|
313 |
- (with-context-variables (session) |
|
314 |
- (require-login |
|
315 |
- (with-endpoints (gethash :endpoint-schema session) |
|
316 |
- (cl-json:encode-json-to-string (gethash :userinfo session)))))) |
|
317 |
- |
|
318 |
-(defun logout-route (params) |
|
319 |
- (declare (ignore params)) |
|
320 |
- (with-context-variables (session) |
|
321 |
- (setf (gethash :userinfo session) nil) |
|
322 |
- '(302 (:location "/")))) |
|
323 |
- |
|
324 |
-(defun oauth2-login-middleware (app &key google-info facebook-info (login-callback #'identity)) |
|
325 |
- (load-facebook-info facebook-info) |
|
326 |
- (load-goog-endpoint-schema) |
|
327 |
- (load-google-info google-info) |
|
328 |
- (setf (route app "/userinfo.json" :method :get) #'userinfo-route |
|
329 |
- (route app "/logout" :method :get) #'logout-route |
|
330 |
- (route app "/login/google" :method :get) #'google-login-entry |
|
331 |
- (route app "/login/facebook" :method :get) #'facebook-login-entry |
|
282 |
+(generate-auth-callback google-callback *goog-endpoint-schema* (a-t) |
|
283 |
+ (labels ((get-real-access-token (a-t) (assoc-cdr :access--token a-t)) |
|
284 |
+ (get-id-token (a-t) (cljwt:decode (assoc-cdr :id--token a-t) :fail-if-unsupported nil))) |
|
285 |
+ (let ((access-token (get-real-access-token a-t))) |
|
286 |
+ (values access-token |
|
287 |
+ (get-user-info *goog-endpoint-schema* access-token) |
|
288 |
+ (get-id-token a-t))))) |
|
289 |
+ |
|
290 |
+(generate-auth-callback facebook-callback *fbook-endpoint-schema* (a-t) |
|
291 |
+ (labels ((get-id-token (a-t) (assoc-cdr :access--token a-t))) |
|
292 |
+ ; ^-- access--token is not a mistake here |
|
293 |
+ (let ((id-token (get-id-token a-t))) |
|
294 |
+ (values a-t (get-user-info *fbook-endpoint-schema* id-token) id-token)))) |
|
295 |
+ |
|
296 |
+(defun initialize-oid-connect (facebook-info google-info) |
|
297 |
+ "Load the Google and Facebook app secrets and initialize Google's openid-configuration |
|
298 |
+ form its well-known document" |
|
299 |
+ (load-provider-secrets *fbook-info* facebook-info) |
|
300 |
+ (load-provider-secrets *goog-info* google-info) |
|
301 |
+ (load-goog-endpoint-schema)) |
|
302 |
+ |
|
303 |
+(defun bind-oid-connect-routes (app &optional (login-callback #'identity)) |
|
304 |
+ (setf (route app "/login/google" :method :get) (lambda (params) (google-login-entry params)) |
|
305 |
+ (route app "/login/facebook" :method :get) (lambda (params) (facebook-login-entry params)) |
|
332 | 306 |
(route app "/oidc_callback/google" :method :get) (google-callback login-callback) |
333 |
- (route app "/oidc_callback/facebook" :method :get) (facebook-callback login-callback)) |
|
334 |
- (lambda (app) (lambda (env) (funcall app env)))) |
|
307 |
+ (route app "/oidc_callback/facebook" :method :get) (facebook-callback login-callback))) |
|
308 |
+ |
|
309 |
+(defmacro setup-oid-connect (app args &body callback) |
|
310 |
+ `(bind-oid-connect-routes ,app (lambda ,args ,@callback))) |
|
335 | 311 |
|
336 | 312 |
(defmacro redirect-if-necessary (sessionvar &body body) |
337 | 313 |
(with-gensyms (session) |
... | ... |
@@ -1,4 +1,6 @@ |
1 | 1 |
(in-package :cl-user) |
2 |
+(ql:quickload :clack-middleware-postmodern) |
|
3 |
+ |
|
2 | 4 |
(ql:quickload :cl-markup) |
3 | 5 |
(ql:quickload :cl-oid-connect) |
4 | 6 |
(ql:quickload :colors) |
... | ... |
@@ -25,7 +27,7 @@ |
25 | 27 |
(load "rss.lisp") |
26 | 28 |
|
27 | 29 |
(defpackage :whitespace |
28 |
- (:use #:cl #:whitespace.utils #:whitespace.feeds.rss #:whitespace.tables)) |
|
30 |
+ (:use #:cl #:anaphora #:whitespace.utils #:whitespace.feeds.rss #:whitespace.tables)) |
|
29 | 31 |
|
30 | 32 |
(in-package plump-dom) |
31 | 33 |
|
... | ... |
@@ -141,18 +143,18 @@ |
141 | 143 |
|
142 | 144 |
; ; ; Ultimately, this will only serialize the feed if the client |
143 | 145 |
(cl-oid-connect:def-route ("/feeds/add" (params) :method :post :app *app*) |
144 |
- (ningle.context:with-context-variables (session) |
|
146 |
+ (ningle.context:with-context-variables (session) |
|
145 | 147 |
(let ((user-info (gethash :app-user session)) |
146 | 148 |
(result '(302 (:location "/"))) |
147 |
- (api (string= (cl-oid-connect:assoc-cdr "api" params 'string=) "yes")) |
|
148 |
- (url (cl-oid-connect:assoc-cdr "url" params 'string=)) |
|
149 |
+ (api (string= (cl-oid-connect:assoc-cdr "api" params 'string=) "yes")) |
|
150 |
+ (url (cl-oid-connect:assoc-cdr "url" params 'string=)) |
|
149 | 151 |
(plump-parser:*tag-dispatchers* plump-parser:*xml-tags*)) |
150 | 152 |
(cl-oid-connect:require-login |
151 | 153 |
(when (neither-null params user-info) |
152 | 154 |
(handler-case |
153 | 155 |
(let* ((doc (plump:parse (drakma:http-request url))) |
154 | 156 |
(uid (slot-value user-info 'id))) |
155 |
- (multiple-value-bind (added-feed dao-feed) (store-feed doc) |
|
157 |
+ (multiple-value-bind (added-feed dao-feed) (store-feed doc) |
|
156 | 158 |
(subscribe-to-feed uid (slot-value dao-feed 'id)) |
157 | 159 |
(when api |
158 | 160 |
(setf result `(200 (:Content-Type "application/json") ,(jsonapi-encoder t added-feed)))))) |
... | ... |
@@ -182,38 +184,7 @@ |
182 | 184 |
collect (elt *feeds* x))))) |
183 | 185 |
(base-template-f))))) |
184 | 186 |
|
185 |
-(defun login-callback (userinfo &rest args) |
|
186 |
- (declare (ignore args)) |
|
187 |
- (postmodern:with-transaction () |
|
188 |
- (let* ((received-id (anaphora:aif (cl-oid-connect:assoc-cdr :id userinfo) |
|
189 |
- anaphora:it |
|
190 |
- (cl-oid-connect:assoc-cdr :sub userinfo))) |
|
191 |
- (db-user (car (postmodern:select-dao 'reader_user (:= :foreign-id received-id))))) |
|
192 |
- (if (not (null db-user)) |
|
193 |
- db-user |
|
194 |
- (progn |
|
195 |
- (let ((name (cl-oid-connect:assoc-cdr :name userinfo)) |
|
196 |
- (first-name (anaphora:aif (cl-oid-connect:assoc-cdr :first--name userinfo) |
|
197 |
- anaphora:it |
|
198 |
- (cl-oid-connect:assoc-cdr :given--name userinfo))) |
|
199 |
- (last-name (anaphora:aif (cl-oid-connect:assoc-cdr :last--name userinfo) |
|
200 |
- anaphora:it |
|
201 |
- (cl-oid-connect:assoc-cdr :family--name userinfo))) |
|
202 |
- (email (cl-oid-connect:assoc-cdr :email userinfo)) |
|
203 |
- (gender (cl-oid-connect:assoc-cdr :gender userinfo)) |
|
204 |
- (link (anaphora:aif (cl-oid-connect:assoc-cdr :link userinfo) |
|
205 |
- anaphora:it |
|
206 |
- (cl-oid-connect:assoc-cdr :profile userinfo))) |
|
207 |
- (locale (cl-oid-connect:assoc-cdr :locale userinfo))) |
|
208 |
- (postmodern:make-dao 'reader_user |
|
209 |
- :foreign-id received-id |
|
210 |
- :first-name first-name |
|
211 |
- :last-name last-name |
|
212 |
- :name name |
|
213 |
- :email email |
|
214 |
- :gender gender |
|
215 |
- :link link |
|
216 |
- :locale locale))))))) |
|
187 |
+ |
|
217 | 188 |
|
218 | 189 |
(cl-oid-connect:def-route ("/demo" (params) :app *app*) |
219 | 190 |
(base-template-f t)) |
... | ... |
@@ -325,14 +296,45 @@ |
325 | 296 |
(colors:let-palette (colors:invert-palette (make-instance 'colors:palette)) |
326 | 297 |
(eval '(get-theme-css)))) |
327 | 298 |
|
328 |
-(defparameter oid-mw |
|
329 |
- (cl-oid-connect:oauth2-login-middleware |
|
330 |
- *app* |
|
331 |
- :facebook-info (truename "~/github_repos/cl-oid-connect/facebook-secrets.json") |
|
332 |
- :google-info (truename "~/github_repos/cl-oid-connect/google-secrets.json") |
|
333 |
- :login-callback #'login-callback)) |
|
299 |
+(cl-oid-connect:def-route ("/userinfo.json" (params) :app *app*) |
|
300 |
+ (declare (ignore params)) |
|
301 |
+ (ningle:with-context-variables (session) |
|
302 |
+ (cl-oid-connect:require-login |
|
303 |
+ (cl-oid-connect::with-endpoints (gethash :endpoint-schema session) |
|
304 |
+ `(200 (:content-type "application/json") ,(cl-json:encode-json-to-string (gethash :userinfo session))))))) |
|
334 | 305 |
|
335 |
-(ql:quickload :clack-middleware-postmodern) |
|
306 |
+(cl-oid-connect:def-route ("/logout" (params) :app *app*) |
|
307 |
+ (declare (ignore params)) |
|
308 |
+ (ningle:with-context-variables (session) |
|
309 |
+ (setf (gethash :userinfo session) nil) |
|
310 |
+ '(302 (:location "/")))) |
|
311 |
+ |
|
312 |
+(defun assoc-cdr-alternatives (alist alt1 alt2 &optional (test #'eql)) |
|
313 |
+ (aif (cl-oid-connect:assoc-cdr alt1 alist test) |
|
314 |
+ it |
|
315 |
+ (cl-oid-connect:assoc-cdr alt2 alist test))) |
|
316 |
+ |
|
317 |
+(cl-oid-connect::setup-oid-connect *app* (userinfo &rest args) |
|
318 |
+ (declare (ignore args) (optimize (speed 0) (safety 3) (debug 3))) |
|
319 |
+ (flet ((get-received-id (userinfo) (assoc-cdr-alternatives userinfo :id :sub)) |
|
320 |
+ (get-db-user (received-id) (car (postmodern:select-dao 'reader_user (:= :foreign-id received-id)))) |
|
321 |
+ (get-first-name (userinfo) (assoc-cdr-alternatives userinfo :first--name :given--name)) |
|
322 |
+ (get-last-name (userinfo) (assoc-cdr-alternatives userinfo :last--name :family--name)) |
|
323 |
+ (get-link (userinfo) (assoc-cdr-alternatives userinfo :link :profile))) |
|
324 |
+ |
|
325 |
+ (postmodern:with-transaction () |
|
326 |
+ (let ((received-id (get-received-id userinfo))) |
|
327 |
+ (aif (get-db-user received-id) it |
|
328 |
+ (postmodern:make-dao |
|
329 |
+ 'reader_user |
|
330 |
+ :foreign-id received-id |
|
331 |
+ :first-name (get-first-name userinfo) |
|
332 |
+ :last-name (get-last-name userinfo) |
|
333 |
+ :name (cl-oid-connect:assoc-cdr :name userinfo) |
|
334 |
+ :email (cl-oid-connect:assoc-cdr :email userinfo) |
|
335 |
+ :gender (cl-oid-connect:assoc-cdr :gender userinfo) |
|
336 |
+ :link (get-link userinfo) |
|
337 |
+ :locale (cl-oid-connect:assoc-cdr :locale userinfo))))))) |
|
336 | 338 |
|
337 | 339 |
(defun update-feed (url) |
338 | 340 |
(with-whitespace-db |
... | ... |
@@ -382,6 +384,9 @@ |
382 | 384 |
(defun stop () (clack:stop (pop handler))) |
383 | 385 |
|
384 | 386 |
(defun start (&optional tmp) |
387 |
+ (cl-oid-connect:initialize-oid-connect |
|
388 |
+ (ubiquitous:value 'facebook 'secrets) |
|
389 |
+ (ubiquitous:value 'google 'secrets)) |
|
385 | 390 |
(let ((server (if (> (length tmp) 1) |
386 | 391 |
(intern (string-upcase (elt tmp 1)) 'keyword) |
387 | 392 |
:hunchentoot))) |
... | ... |
@@ -400,6 +405,6 @@ |
400 | 405 |
(defun restart-clack () |
401 | 406 |
(do () ((null handler)) (stop)) |
402 | 407 |
(start))) |
403 |
- |
|
408 |
+ |
|
404 | 409 |
|
405 | 410 |
; vim: foldmethod=marker foldmarker=(,) foldminlines=3 : |