git.fiddlerwoaroof.com
Browse code

Generalizing for use in other apps

fiddlerwoaroof authored on 29/08/2015 23:22:08
Showing 3 changed files
... ...
@@ -12,6 +12,7 @@
12 12
                :anaphora
13 13
                :alexandria
14 14
                :lack-middleware-session
15
+               :sheeple
15 16
                :cl-who)
16 17
   :serial t
17 18
   :components ((:file "package")
... ...
@@ -33,41 +33,15 @@
33 33
 |#
34 34
 
35 35
 (in-package :cl-oid-connect)
36
+(declaim (optimize (debug 2)))
36 37
 ; Should this be here?
38
+(defparameter *oid* (make-instance 'ningle:<app>))
37 39
 (setf drakma:*text-content-types* (cons '("application" . "json") drakma:*text-content-types*))
38 40
 
39 41
 (sheeple:defproto =service-info= ()
40 42
                   ((client-id nil :accessor t)
41 43
                    (secret nil :accessor t)))
42 44
 
43
-(defvar *FBOOK-INFO* (sheeple:clone =service-info=))
44
-(defun load-facebook-info (loadfrom)
45
-  (setf *FBOOK-INFO* 
46
-        (with-open-file (fbook-info (truename loadfrom))
47
-          (let* ((data (yason:parse fbook-info))
48
-                 (client-id (gethash "client-id" data))
49
-                 (secret (gethash "secret" data)))
50
-            (sheeple:defobject (=service-info=)
51
-                               ((client-id client-id)
52
-                                (secret secret)))))))
53
-
54
-(defvar *GOOG-INFO* (sheeple:clone =service-info=))
55
-(defun load-google-info (loadfrom)
56
-  (setf *GOOG-INFO*
57
-        (with-open-file (goog-info (truename loadfrom))
58
-          (let* ((data (yason:parse goog-info))
59
-                 (client-id (gethash "client-id" data))
60
-                 (secret (gethash "secret" data)))
61
-            (sheeple:defobject (=service-info=)
62
-                               ((client-id client-id)
63
-                                (secret secret)))))))
64
-
65
-(load-facebook-info #p"facebook-secrets.json")
66
-(load-google-info #p"google-secrets.json")
67
-
68
-(defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal))
69
-(defmacro assoc-cdr (key alist) `(cdr (assoc ,key ,alist)))
70
-
71 45
 (sheeple:defproto =endpoint-schema= ()
72 46
                   ((auth-endpoint nil :accessor t)
73 47
                    (token-endpoint nil :accessor t)
... ...
@@ -78,42 +52,11 @@
78 52
 (sheeple:defreply get-user-info ((a =endpoint-schema=) (b sheeple:=string=)))
79 53
 (sheeple:defreply get-access-token ((a =endpoint-schema=) (b sheeple:=string=)))
80 54
 
81
-(defun discover-endpoints (service-info discovery-doc-url &key (gat nil gat-p) (gui nil gui-p))
82
-  (let ((discovery-document (cl-json:decode-json-from-string (drakma:http-request discovery-doc-url)))
83
-        (schema (sheeple:object :parents `(,=endpoint-schema= ,service-info))))
84
-
85
-    (setf (auth-endpoint schema) (assoc-cdr :authorization--endpoint discovery-document))
86
-    (setf (token-endpoint schema) (assoc-cdr :token--endpoint discovery-document))
87
-
88
-    (if gui-p (sheeple:defreply get-user-info ((a schema)) (funcall gui a)))
89
-    (if gat-p (sheeple:defreply get-access-token ((a schema) (b sheeple:=string=))
90
-                (funcall gat a b)))
91
-
92
-    schema))
93
-
94
-; This probably should eventually go?
95
-(defvar *endpoint-schema* nil)
96
-(defmacro with-endpoints (endpoint-schema  &body body)
97
-  `(let* ((*endpoint-schema* ,endpoint-schema))
98
-     ,@body))
99
-
100
-(defun goog-get-access-token (endpoint-schema code)
101
-  (cl-json:decode-json-from-string
102
-    (drakma:http-request (token-endpoint endpoint-schema)
103
-                         :method :post
104
-                         :redirect nil
105
-                         :parameters `(("code" . ,code)
106
-                                       ("client_id" . ,(client-id endpoint-schema))
107
-                                       ("client_secret" . ,(secret endpoint-schema))
108
-                                       ("redirect_uri" . ,(redirect-uri endpoint-schema))
109
-                                       ("grant_type" . "authorization_code")))))
110
-
55
+(defparameter *FBOOK-INFO* (sheeple:clone =service-info=))
56
+(defparameter *GOOG-INFO* (sheeple:clone =service-info=))
57
+(defparameter *endpoint-schema* nil)
111 58
 ; goog is well behaved
112
-(defvar *goog-endpoint-schema*
113
-  (discover-endpoints *GOOG-INFO* "https://accounts.google.com/.well-known/openid-configuration"
114
-                      :gat #'goog-get-access-token))
115
-(setf (redirect-uri *goog-endpoint-schema*)   "http://srv2.elangley.org:9090/oidc_callback/google")
116
-
59
+(defparameter *goog-endpoint-schema* (defobject (=endpoint-schema= *goog-info*)))
117 60
 
118 61
 ; fbook needs personal attention
119 62
 (defproto *fbook-endpoint-schema* (=endpoint-schema= *fbook-info*)
... ...
@@ -142,18 +85,25 @@
142 85
       (drakma:http-request endpoint
143 86
                            :parameters `(("access_token" . ,access-token))))))
144 87
 
145
-(defvar *fbook-mw*
146
-  (lambda (app)
147
-    (lambda (env)
148
-      (with-fbook-endpoints
149
-        (format t "~a" *client-id*)
150
-        (funcall app env)))))
88
+(defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal))
89
+(defmacro assoc-cdr (key alist) `(cdr (assoc ,key ,alist)))
151 90
 
152
-(defvar *goog-mw*
153
-  (lambda (app)
154
-    (lambda (env)
155
-      (with-goog-endpoints
156
-        (funcall app env)))))
91
+(defun discover-endpoints (schema discovery-doc-url &key (gat nil gat-p) (gui nil gui-p))
92
+  (let ((discovery-document (cl-json:decode-json-from-string (drakma:http-request discovery-doc-url))))
93
+
94
+    (setf (auth-endpoint schema) (assoc-cdr :authorization--endpoint discovery-document))
95
+    (setf (token-endpoint schema) (assoc-cdr :token--endpoint discovery-document))
96
+
97
+    (if gui-p (sheeple:defreply get-user-info ((a schema)) (funcall gui a)))
98
+    (if gat-p (sheeple:defreply get-access-token ((a schema) (b sheeple:=string=))
99
+                (funcall gat a b)))
100
+
101
+    schema))
102
+
103
+; This probably should eventually go?
104
+(defmacro with-endpoints (endpoint-schema  &body body)
105
+  `(let* ((*endpoint-schema* ,endpoint-schema))
106
+     ,@body))
157 107
 
158 108
 (defun do-auth-request (endpoint-schema state)
159 109
   (format t "~%client-id: ~a~%" (auth-endpoint endpoint-schema))
... ...
@@ -172,9 +122,9 @@
172 122
       (loop repeat len
173 123
             do (princ (random 36) stream)))))
174 124
 
175
-(defvar *app* (make-instance 'ningle:<app>))
176
-(defmacro def-route (url args &body body)
177
-  `(setf (ningle:route *app* ,url)
125
+
126
+(defmacro def-route ((url args &key (app *oid*)) &body body)
127
+  `(setf (ningle:route ,app ,url)
178 128
          #'(lambda ,args
179 129
              (declare (ignorable ,@args))
180 130
              ,@body)))
... ...
@@ -201,68 +151,131 @@
201 151
   `(let ((,var (context :session)))
202 152
      ,@body))
203 153
 
154
+(defun load-facebook-info (loadfrom)
155
+  (with-open-file (fbook-info (truename loadfrom))
156
+    (let* ((data (yason:parse fbook-info))
157
+           (client-id (gethash "client-id" data))
158
+           (secret (gethash "secret" data)))
159
+      (setf (client-id *FBOOK-INFO*) client-id) 
160
+      (setf (secret *FBOOK-INFO*) secret))))
204 161
 
205
-(def-route "/login/google" (params)
206
-  (with-session (session)
207
-    (let ((state (gen-state 36)))
208
-      (setf (gethash :state session) state)
209
-      (with-endpoints *goog-endpoint-schema*
210
-        (setf (gethash :endpoint-schema session) *goog-endpoint-schema*)
211
-        (multiple-value-bind (content rcode headers) (do-auth-request *goog-endpoint-schema* state)
212
-          (if (< rcode 400)
213
-            `(302 (:location ,(cdr (assoc :location headers))))
214
-            content))))))
162
+(defun load-google-info (loadfrom)
163
+  (with-open-file (goog-info (truename loadfrom))
164
+    (let* ((data (yason:parse goog-info))
165
+           (client-id (gethash "client-id" data))
166
+           (secret (gethash "secret" data)))
167
+      (setf (client-id *GOOG-INFO*) client-id)
168
+      (setf (secret *GOOG-INFO*) secret))))
215 169
 
170
+(defun goog-get-access-token (endpoint-schema code)
171
+  (cl-json:decode-json-from-string
172
+    (drakma:http-request (token-endpoint endpoint-schema)
173
+                         :method :post
174
+                         :redirect nil
175
+                         :parameters `(("code" . ,code)
176
+                                       ("client_id" . ,(client-id endpoint-schema))
177
+                                       ("client_secret" . ,(secret endpoint-schema))
178
+                                       ("redirect_uri" . ,(redirect-uri endpoint-schema))
179
+                                       ("grant_type" . "authorization_code")))))
216 180
 
217
-(def-route "/login/facebook" (params)
218
-  (with-session (session)
219
-    (let ((state (gen-state 36)))
220
-      (setf (gethash :state session) state)
221
-      (with-endpoints *fbook-endpoint-schema*
222
-        (setf (gethash :endpoint-schema session) *fbook-endpoint-schema*)
223
-        (multiple-value-bind (content rcode headers uri) (do-auth-request *fbook-endpoint-schema* state)
224
-          (if (< rcode 400)
225
-            `(302 (:location ,(format nil "~a" uri)))
226
-            content))))))
227
-
228
-(def-route "/oidc_callback/google" (params)
229
-  (let ((received-state (cdr (string-assoc "state" params)))
230
-        (code (cdr (string-assoc "code" params))))
231
-    (check-state received-state
232
-                 (with-session (session)
233
-                   (with-endpoints *goog-endpoint-schema*
234
-                     (let* ((a-t (get-access-token *goog-endpoint-schema* code))
235
-                            (id-token (assoc-cdr :id--token a-t))
236
-                            (decoded (cljwt:decode id-token :fail-if-unsupported nil)))
237
-                       (setf (gethash :userinfo session) decoded)
238
-                       '(302 (:location "/")))))
239
-                 '(403 '() "Out, vile imposter!"))))
240
-
241
-
242
-(def-route "/oidc_callback/facebook" (params)
243
-  (let ((received-state (cdr (string-assoc "state" params)))
244
-        (code (cdr (string-assoc "code" params))))
245
-    (with-endpoints *fbook-endpoint-schema*
246
-      (check-state received-state
247
-                   (with-session (session)
248
-                     (let* ((a-t (get-access-token *fbook-endpoint-schema* code))
249
-                            (id-token (assoc-cdr :access--token a-t)))
250
-                       (setf (gethash :userinfo session) (get-user-info *fbook-endpoint-schema* id-token))
251
-                       '(302 (:location "/"))))
252
-                   '(403 '() "Out, vile imposter!")))))
253
-
254
-(def-route "/userinfo.json" (params)
255
-  (with-session (session)
256
-    (require-login 
257
-      (with-endpoints  (gethash :endpoint-schema session)
258
-        (cl-json:encode-json-to-string (gethash :userinfo session))))))
181
+(defun load-goog-endpoint-schema ()
182
+  (discover-endpoints *goog-endpoint-schema*
183
+                      "https://accounts.google.com/.well-known/openid-configuration"
184
+                      :gat #'goog-get-access-token)
185
+  (setf (redirect-uri *goog-endpoint-schema*)   "http://srv2.elangley.org:9090/oidc_callback/google"))
259 186
 
260
-(def-route "/logout" (params)
261
-  (with-session (session)
262
-    (setf (gethash :userinfo session) nil)
263
-    '(302 (:location "/"))))
264 187
 
265
-(def-route "/login" (params)
188
+(defun oauth2-login-middleware (&key google-info facebook-info)
189
+  (lambda (app)
190
+    (load-facebook-info facebook-info)
191
+    (load-goog-endpoint-schema)
192
+    (load-google-info google-info)
193
+
194
+    (def-route ("/login/google" (params) :app app)
195
+      (with-session (session)
196
+        (let ((state (gen-state 36)))
197
+          (setf (gethash :state session) state)
198
+          (with-endpoints *goog-endpoint-schema*
199
+            (setf (gethash :endpoint-schema session) *goog-endpoint-schema*)
200
+            (multiple-value-bind (content rcode headers) (do-auth-request *goog-endpoint-schema* state)
201
+              (if (< rcode 400)
202
+                `(302 (:location ,(cdr (assoc :location headers))))
203
+                content))))))
204
+
205
+
206
+    (def-route ("/login/facebook" (params) :app app)
207
+      (with-session (session)
208
+        (let ((state (gen-state 36)))
209
+          (setf (gethash :state session) state)
210
+          (with-endpoints *fbook-endpoint-schema*
211
+            (setf (gethash :endpoint-schema session) *fbook-endpoint-schema*)
212
+            (multiple-value-bind (content rcode headers uri) (do-auth-request *fbook-endpoint-schema* state)
213
+              (declare (ignore headers))
214
+              (if (< rcode 400)
215
+                `(302 (:location ,(format nil "~a" uri)))
216
+                content))))))
217
+
218
+    (def-route ("/oidc_callback/google" (params) :app app)
219
+      (let ((received-state (cdr (string-assoc "state" params)))
220
+            (code (cdr (string-assoc "code" params))))
221
+        (check-state received-state
222
+                     (with-session (session)
223
+                       (with-endpoints *goog-endpoint-schema*
224
+                         (let* ((a-t (get-access-token *goog-endpoint-schema* code))
225
+                                (id-token (assoc-cdr :id--token a-t))
226
+                                (decoded (cljwt:decode id-token :fail-if-unsupported nil)))
227
+                           (setf (gethash :userinfo session) decoded)
228
+                           '(302 (:location "/")))))
229
+                     '(403 '() "Out, vile imposter!"))))
230
+
231
+
232
+    (def-route ("/oidc_callback/facebook" (params) :app app)
233
+      (let ((received-state (cdr (string-assoc "state" params)))
234
+            (code (cdr (string-assoc "code" params))))
235
+        (with-endpoints *fbook-endpoint-schema*
236
+          (check-state received-state
237
+                       (with-session (session)
238
+                         (let* ((a-t (get-access-token *fbook-endpoint-schema* code))
239
+                                (id-token (assoc-cdr :access--token a-t)))
240
+                           (setf (gethash :userinfo session) (get-user-info *fbook-endpoint-schema* id-token))
241
+                           '(302 (:location "/"))))
242
+                       '(403 '() "Out, vile imposter!")))))
243
+
244
+    (def-route ("/userinfo.json" (params) :app app)
245
+      (with-session (session)
246
+        (require-login 
247
+          (with-endpoints  (gethash :endpoint-schema session)
248
+            (cl-json:encode-json-to-string (gethash :userinfo session))))))
249
+
250
+    (def-route ("/logout" (params) :app app)
251
+      (with-session (session)
252
+        (setf (gethash :userinfo session) nil)
253
+        '(302 (:location "/"))))
254
+
255
+    app))
256
+
257
+
258
+(defmacro redirect-if-necessary (sessionvar &body body)
259
+  (with-gensyms (session)
260
+    `(let* ((,session ,sessionvar)
261
+            (next-page (gethash :next-page ,session)))
262
+       (if (and (not (null next-page))
263
+                (not (string= next-page (lack.request:request-path-info *request*))))
264
+         (progn
265
+           (setf (gethash :next-page ,session) nil)
266
+           `(302 (:location ,next-page)))
267
+         ,@body))))
268
+
269
+(export '(redirect-if-necessary def-route require-login))
270
+(export '(oauth2-login-middleware with-session))
271
+
272
+(in-package :cl-user)
273
+(import '(cl-oid-connect:redirect-if-necessary cl-oid-connect:def-route cl-oid-connect:require-login))
274
+(import '(cl-oid-connect:oauth2-login-middleware cl-oid-connect:with-session))
275
+
276
+(defparameter *app* (make-instance 'ningle:<app>))
277
+
278
+(def-route ("/login" (params) :app *app*)
266 279
   (cl-who:with-html-output-to-string (s)
267 280
     (:html
268 281
       (:head
... ...
@@ -271,16 +284,23 @@
271 284
         (:div (:a :href "/login/facebook" "Facebook"))
272 285
         (:div (:a :href "/login/google" "Google")))))) 
273 286
 
274
-(def-route "/" (params)
287
+(def-route ("/" (params) :app *app*)
275 288
   (with-session (session)
276
-    (if (not (null (gethash :next-page session)))
277
-      `(302 (:location ,(gethash :next-page session)))
289
+    (redirect-if-necessary session
278 290
       (require-login 
279 291
         (anaphora:sunless (gethash :counter session) (setf anaphora:it 0))
292
+        (incf (gethash :counter session))
280 293
         (format nil "~Ath visit<br/>~a<br/><br/>~S<br/>"
281 294
                 (gethash :counter session)
282 295
                 (alexandria:hash-table-alist session)
283
-                (alexandria:hash-table-alist (context :session)))))))
284
-
285
-(setf *handler* (clack:clackup (lack.builder:builder :session *app*) :port 9090))
296
+                (alexandria:hash-table-alist (ningle:context :session)))))))
297
+
298
+(setf *handler* (clack:clackup (lack.builder:builder
299
+                                 :backtrace
300
+                                 :session
301
+                                 (funcall
302
+                                   (oauth2-login-middleware
303
+                                     :facebook-info (truename "facebook-secrets.json") 
304
+                                     :google-info (truename "google-secrets.json"))
305
+                                   *app*)) :port 9090))
286 306
 
... ...
@@ -1,14 +1,14 @@
1 1
 ;;;; package.lisp
2
-(ql:quickload :ningle)
3
-(ql:quickload :clack)
4
-(ql:quickload :drakma)
5
-(ql:quickload :cljwt)
6
-(ql:quickload :cl-json)
7
-(ql:quickload :anaphora)
8
-(ql:quickload :alexandria)
9
-(ql:quickload :lack-middleware-session)
10
-(ql:quickload :cl-who)
11
-(ql:quickload :sheeple)
2
+;(ql:quickload :ningle)
3
+;(ql:quickload :clack)
4
+;(ql:quickload :drakma)
5
+;(ql:quickload :cljwt)
6
+;(ql:quickload :cl-json)
7
+;(ql:quickload :anaphora)
8
+;(ql:quickload :alexandria)
9
+;(ql:quickload :lack-middleware-session)
10
+;(ql:quickload :cl-who)
11
+;(ql:quickload :sheeple)
12 12
 
13 13
 (defpackage :cl-oid-connect
14 14
   (:use #:cl #:drakma #:ningle #:clack #:cljwt #:anaphora #:alexandria #:sheeple))