git.fiddlerwoaroof.com
Browse code

splitting cl-oid-connect off

fiddlerwoaroof authored on 05/11/2015 00:23:46
Showing 2 changed files
1 1
deleted file mode 100644
... ...
@@ -1,27 +0,0 @@
1
-;;;; cl-oid-connect.asd
2
-
3
-(asdf:defsystem :cl-oid-connect
4
-  :description "A Common Lisp Implementation of Various OAuth2 Authentication Protocols"
5
-  :author "Ed L <(format nil \"~a@~a\" \"el-projects\" \"howit.is\")>"
6
-  :license "2=Clause BSD"
7
-  :depends-on (:drakma
8
-                :ningle
9
-                :clack
10
-                :cljwt
11
-                :cl-json
12
-                :anaphora
13
-                :alexandria
14
-                :lack-middleware-session
15
-                :sheeple
16
-                :lass
17
-                :lquery
18
-                :plump
19
-                :cl-who
20
-                :postmodern
21
-                :iterate)
22
-  :serial t
23
-  :components ((:file "utils")
24
-               (:file "package")
25
-               (:file "cl-oid-connect")))
26
-
27
-
28 0
deleted file mode 100644
... ...
@@ -1,321 +0,0 @@
1
-;;;; cl-oid-connect.lisp
2
-;;;; TODO: Need to refactor out server names!!!
3
-
4
-#|
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
- |#
36
-
37
-(in-package :cl-oid-connect)
38
-; Should this be here?
39
-
40
-(eval-when (:compile-toplevel :execute :load-toplevel)
41
-  (defun vars-to-symbol-macrolets (vars obj)
42
-    (iterate:iterate (iterate:for (store key) in (ensure-mapping vars))
43
-                     (iterate:collect `(,store (gethash ,(alexandria:make-keyword key) ,obj))))))
44
-
45
-(defmacro with-session-values (vars session &body body)
46
-  (alexandria:once-only (session)
47
-    `(symbol-macrolet ,(vars-to-symbol-macrolets vars session)
48
-       ,@body)))
49
-
50
-; This probably should eventually go?
51
-(defmacro with-endpoints (endpoint-schema  &body body)
52
-  `(let* ((*endpoint-schema* ,endpoint-schema))
53
-     ,@body))
54
-
55
-(defmacro with-session ((var) &body body)
56
-  `(progn
57
-     (format t "The session var is: ~a it contains: ~a~%"  ,(symbol-name var) ,var)
58
-     (let ((,var (context :session)))
59
-       (format t "The session var is: ~a it now contains: ~a~%"  ,(symbol-name var) ,var)
60
-       ,@body)))
61
-
62
-(defmacro def-route ((url args &key (app *oid*) (method :GET)) &body body)
63
-  `(setf (ningle:route ,app ,url :method ,method)
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))
87
-
88
-(defparameter *oid* (make-instance 'ningle:<app>))
89
-(setf drakma:*text-content-types* (cons '("application" . "json") drakma:*text-content-types*))
90
-
91
-(setf =service-info= (object :parents '()
92
-                             :properties '((client-id nil :accessor client-id)
93
-                                           (secret nil :accessor secret))))
94
-
95
-(setf =endpoint-schema= (object :parents '()
96
-                                :properties '((auth-endpoint nil :accessor auth-endpoint)
97
-                                              (token-endpoint nil :accessor token-endpoint)
98
-                                              (userinfo-endpoint nil :accessor t)
99
-                                              (auth-scope "openid profile email" :accessor t)
100
-                                              (redirect-uri nil :accessor t))))
101
-
102
-(sheeple:defmessage get-user-info (a b))
103
-(sheeple:defmessage get-access-token (a b))
104
-
105
-(sheeple:defreply get-user-info ((a =endpoint-schema=) (b sheeple:=string=)))
106
-(sheeple:defreply get-access-token ((a =endpoint-schema=) (b sheeple:=string=)))
107
-
108
-(defparameter *endpoint-schema* nil)
109
-(defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal))
110
-(defmacro assoc-cdr (key alist &optional (test '#'eql)) `(cdr (assoc ,key ,alist :test ,test)))
111
-
112
-(defun discover-endpoints (schema discovery-doc-url &key (gat nil gat-p) (gui nil gui-p))
113
-  "Discover endpoints on the basis of a discovery document stored at a particular url.
114
-   The two keyword arguments define a function to bind to sheeple replies for get-user-token
115
-   and get-access-token."
116
-  (prog1 schema
117
-    (let ((discovery-document (cl-json:decode-json-from-string (drakma:http-request discovery-doc-url))))
118
-      (setf (auth-endpoint schema) (assoc-cdr :authorization--endpoint discovery-document)
119
-            (token-endpoint schema) (assoc-cdr :token--endpoint discovery-document)
120
-            (userinfo-endpoint schema) (assoc-cdr :userinfo--endpoint discovery-document))
121
-      (when gui-p
122
-        (format t "defining gui-p")
123
-        (sheeple:defreply get-user-info ((a schema)) (funcall gui a)))
124
-      (when gat-p
125
-        (format t "defining gat-p")
126
-        (sheeple:defreply get-access-token ((a schema) (b sheeple:=string=))
127
-          (funcall gat a b))))))
128
-
129
-(defun do-auth-request (endpoint-schema state)
130
-  (drakma:http-request (auth-endpoint endpoint-schema)
131
-                       :redirect nil
132
-                       :parameters `(("client_id" . ,(client-id endpoint-schema))
133
-                                     ("app_id" . ,(client-id endpoint-schema))
134
-                                     ("response_type" . "code")
135
-                                     ("scope" . ,(auth-scope endpoint-schema))
136
-                                     ("redirect_uri" . ,(redirect-uri endpoint-schema))
137
-                                     ("state" . ,state))))
138
-
139
-(defmacro auth-entry-point (name endpoint-schema)
140
-  `(defun ,name (params)
141
-     (declare (ignore params))
142
-     (with-session-values (state endpoint-schema) (context :session)
143
-       (setf state (gen-state 36)
144
-             endpoint-schema ,endpoint-schema)
145
-       (with-endpoints ,endpoint-schema
146
-         (multiple-value-bind (content rcode headers uri) (do-auth-request ,endpoint-schema state)
147
-           (declare (ignore headers))
148
-           (if (< rcode 400) `(302 (:location ,(format nil "~a" uri)))
149
-             content))))))
150
-
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)))
153
-    (let ((a-t (get-access-token endpoint-schema (get-code params))))
154
-      (auth-callback-skeleton params (:endpoint-schema endpoint-schema
155
-                                      :auth-session-vars (accesstoken userinfo idtoken app-user))
156
-        (multiple-value-bind (access-token user-info id-token) (funcall get-login-data a-t)
157
-          (setf accesstoken access-token
158
-                userinfo user-info
159
-                idtoken id-token
160
-                app-user (funcall get-app-user-cb user-info id-token access-token)))
161
-        '(302 (:location "/"))))))
162
-
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))))))
171
-
172
-(defmacro reject-when-state-invalid (params &body body)
173
-  (alexandria:with-gensyms (received-state)
174
-    (alexandria:once-only (params)
175
-      `(let ((,received-state (cdr (string-assoc "state" ,params))))
176
-         (if (not (valid-state ,received-state))
177
-           '(403 '() "Out, vile imposter!")
178
-        ,@body)))))
179
-
180
-(defmacro auth-callback-skeleton (params (&key endpoint-schema auth-session-vars) &body body)
181
-  (alexandria:with-gensyms (session)
182
-    (alexandria:once-only (params endpoint-schema)
183
-      `(reject-when-state-invalid ,params
184
-         (with-endpoints ,endpoint-schema
185
-           (my-with-context-variables ((,session session))
186
-             ,(if (null auth-session-vars)
187
-                `(progn
188
-                   ,@body)
189
-                `(with-session-values ,auth-session-vars ,session
190
-                   ,@body))))))))
191
-
192
-(define-condition user-not-logged-in (error) ())
193
-
194
-(defmacro ensure-logged-in (&body body)
195
-  "Ensure that the user is logged in: otherwise throw the condition user-not-logged-in"
196
-  (alexandria:with-gensyms (session userinfo)
197
-    `(my-with-context-variables ((,session session))
198
-       (with-session-values ((,userinfo userinfo)) ,session
199
-         (if (null ,userinfo)
200
-           (error 'user-not-logged-in)
201
-           (progn ,@body))))))
202
-
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))))
207
-
208
-  (defmacro check-login (&body body)
209
-    "Returns an HTTP 401 Error if not logged in."
210
-    (handle-no-user body `('(401 () "Unauthorized"))))
211
-
212
-  (defmacro require-login (&body body)
213
-    "Redirects to /login if not logged in."
214
-    (handle-no-user body
215
-                    `((with-session-values (next-page) (context :session)
216
-                        (setf next-page (lack.request:request-path-info *request*))
217
-                        '(302 (:location "/login")))))))
218
-
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)))
253
-
254
-(defun goog-get-access-token (endpoint-schema code)
255
-  (cl-json:decode-json-from-string
256
-    (drakma:http-request (token-endpoint endpoint-schema)
257
-                         :method :post
258
-                         :redirect nil
259
-                         :parameters `(("code" . ,code)
260
-                                       ("client_id" . ,(client-id endpoint-schema))
261
-                                       ("client_secret" . ,(secret endpoint-schema))
262
-                                       ("redirect_uri" . ,(redirect-uri endpoint-schema))
263
-                                       ("grant_type" . "authorization_code")))))
264
-
265
-(defun load-goog-endpoint-schema ()
266
-  (discover-endpoints *goog-endpoint-schema*
267
-                      "https://accounts.google.com/.well-known/openid-configuration"
268
-                      :gat #'goog-get-access-token)
269
-  (setf (redirect-uri *goog-endpoint-schema*) "http://srv2.elangley.org:9090/oidc_callback/google"))
270
-
271
-(sheeple:defreply get-user-info ((endpoint-schema *goog-endpoint-schema*) (access-token sheeple:=string=))
272
-  (format t "getting user data: ~a~%" "blarg")
273
-  (let ((endpoint (userinfo-endpoint endpoint-schema)))
274
-    (cl-json:decode-json-from-string
275
-      (drakma:http-request endpoint
276
-                           :parameters `(("alt" . "json")
277
-                                         ("access_token" . ,access-token))))))
278
-
279
-(auth-entry-point google-login-entry *goog-endpoint-schema*)
280
-(auth-entry-point facebook-login-entry *fbook-endpoint-schema*)
281
-
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))
306
-        (route app "/oidc_callback/google" :method :get) (google-callback login-callback)
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)))
311
-
312
-(defmacro redirect-if-necessary (sessionvar &body body)
313
-  (with-gensyms (session)
314
-    `(let* ((,session ,sessionvar)
315
-            (next-page (gethash :next-page ,session)))
316
-       (if (and (not (null next-page))
317
-                (not (string= next-page (lack.request:request-path-info *request*))))
318
-         (progn
319
-           (setf (gethash :next-page ,session) nil)
320
-           `(302 (:location ,next-page)))
321
-         ,@body))))