git.fiddlerwoaroof.com
Browse code

This now authorizes against both Google and Facebook

fiddlerwoaroof authored on 22/08/2015 23:16:49
Showing 3 changed files
... ...
@@ -1,2 +1,2 @@
1
-google-secrets.json
1
+*-secrets.json
2 2
 .*.sw?
... ...
@@ -35,12 +35,19 @@
35 35
 (in-package :cl-oid-connect)
36 36
 (setq drakma:*text-content-types* (cons '("application" . "json") drakma:*text-content-types*))
37 37
 
38
+(with-open-file (fbook-info #P"facebook-secrets.json")
39
+  (let* ((data (yason:parse fbook-info))
40
+         (client-id (gethash "client-id" data))
41
+         (secret (gethash "secret" data)))
42
+    (defvar *FBOOK-CLIENT-ID* client-id)
43
+    (defvar *FBOOK-CLIENT-SECRET* secret)))
44
+
38 45
 (with-open-file (goog-info #P"google-secrets.json")
39 46
   (let* ((data (yason:parse goog-info))
40 47
          (client-id (gethash "client-id" data))
41 48
          (secret (gethash "secret" data)))
42
-    (defconstant *GOOG-CLIENT-ID* client-id)
43
-    (defconstant *GOOG-CLIENT-SECRET* secret)))
49
+    (defvar *GOOG-CLIENT-ID* client-id)
50
+    (defvar *GOOG-CLIENT-SECRET* secret)))
44 51
 
45 52
 ;;; "cl-oid-connect" goes here. Hacks and glory await!
46 53
 (defvar *app* (make-instance 'ningle:<app>))
... ...
@@ -50,6 +57,11 @@
50 57
 ;; functions are wrapped with the "with-goog-endpoints" macro.
51 58
 (defvar *auth-endpoint* nil)
52 59
 (defvar *token-endpoint* nil)
60
+(defvar *client-id* nil)
61
+(defvar *client-secret* nil)
62
+(defvar *user-info-cb* (lambda ()))
63
+(defvar *get-access-token* (lambda ()))
64
+(defvar *redirect-uri* nil)
53 65
 
54 66
 (defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal))
55 67
 (defmacro assoc-cdr (key alist) `(cdr (assoc ,key ,alist)))
... ...
@@ -60,9 +72,37 @@
60 72
               (cl-json:decode-json-from-string
61 73
                 (drakma:http-request "https://accounts.google.com/.well-known/openid-configuration")))
62 74
             (*auth-endpoint* (assoc-cdr :authorization--endpoint ,discovery-document))
63
-            (*token-endpoint* (assoc-cdr :token--endpoint ,discovery-document)))
75
+            (*token-endpoint* (assoc-cdr :token--endpoint ,discovery-document))
76
+            (*client-id* *GOOG-CLIENT-ID*)
77
+            (*client-secret* *GOOG-CLIENT-SECRET*)
78
+            (*redirect-uri* "http://srv2.elangley.org:9090/oidc_callback/google")
79
+            )
64 80
        ,@body)))     
65 81
 
82
+
83
+(defmacro with-fbook-endpoints (&body body)
84
+  `(let* ((*auth-endpoint* "https://www.facebook.com/dialog/oauth")
85
+          (*token-endpoint* "https://graph.facebook.com/v2.3/oauth/access_token")
86
+          (*client-id* *FBOOK-CLIENT-ID*)
87
+          (*client-secret* *FBOOK-CLIENT-SECRET*)
88
+          (*user-info-cb* #'fb-get-userinfo)
89
+          (*get-access-token* #'fb-get-access-token)
90
+          (*redirect-uri* "http://srv2.elangley.org:9090/oidc_callback/facebook"))
91
+     ,@body))     
92
+
93
+(defun fb-get-userinfo (access-token)
94
+  (let ((endpoint "https://graph.facebook.com/v2.3/me"))
95
+    (cl-json:decode-json-from-string
96
+      (drakma:http-request endpoint
97
+                           :parameters `(("access_token" . ,access-token))))))
98
+
99
+(defvar *fbook-mw*
100
+  (lambda (app)
101
+    (lambda (env)
102
+      (with-fbook-endpoints
103
+        (format t "~a" *client-id*)
104
+        (funcall app env)))))
105
+
66 106
 (defvar *goog-mw*
67 107
   (lambda (app)
68 108
     (lambda (env)
... ...
@@ -75,18 +115,21 @@
75 115
                          :method :post
76 116
                          :redirect nil
77 117
                          :parameters `(("code" . ,code)
78
-                                       ("client_id" . ,*GOOG-CLIENT-ID*)
79
-                                       ("client_secret" . ,*GOOG-CLIENT-SECRET*)
80
-                                       ("redirect_uri" . "http://srv2.elangley.org:9090/oidc_callback")
118
+                                       ("client_id" . ,*client-id*)
119
+                                       ("app_id" . ,*client-id*)
120
+                                       ("client_secret" . ,*client-secret*)
121
+                                       ("redirect_uri" . ,*redirect-uri*)
81 122
                                        ("grant_type" . "authorization_code")))))
82 123
 
83 124
 (defun do-auth-request (state)
125
+  (format t "~%client-id: ~a~%" *client-id*)
84 126
   (drakma:http-request *auth-endpoint*
85 127
                        :redirect nil
86
-                       :parameters `(("client_id" . ,*GOOG-CLIENT-ID*)
128
+                       :parameters `(("client_id" . ,*client-id*)
129
+                                       ("app_id" . ,*client-id*)
87 130
                                      ("response_type" . "code")
88
-                                     ("scope" . "openid email")
89
-                                     ("redirect_uri" . "http://srv2.elangley.org:9090/oidc_callback")
131
+                                     ("scope" . "email")
132
+                                     ("redirect_uri" . ,*redirect-uri*)
90 133
                                      ("state" . ,state))))
91 134
 
92 135
 (defun gen-state (len)
... ...
@@ -98,50 +141,116 @@
98 141
 (defmacro def-route (url args &body body)
99 142
   `(setf (ningle:route *app* ,url)
100 143
          #'(lambda ,args
144
+             (declare (ignorable ,@args))
101 145
              ,@body)))
102 146
 
103 147
 (defmacro check-state (received-state then else)
104
-  (alexandria:with-gensyms (saved-state)
105
-    `(let ((,saved-state (gethash :state *session*)))
148
+  (alexandria:with-gensyms (saved-state session)
149
+    `(let* ((,session (context :session))
150
+            (,saved-state (gethash :state ,session)))
106 151
        (if (equal ,saved-state ,received-state)
107 152
          ,then
108 153
          ,else))))
109 154
 
110 155
 (defmacro require-login (&body body)
111
-  `(if (not (eql nil (gethash :userinfo *session*)))
112
-     (progn
113
-       ,@body)
114
-     '(302 (:location "/login"))))
156
+  (alexandria:with-gensyms (session)
157
+    `(let ((,session (context :session)))
158
+       (if (not (eql nil (gethash :userinfo ,session)))
159
+         (progn
160
+           ,@body)
161
+         '(302 (:location "/login"))))))
162
+
163
+(defmacro with-session ((var) &body body)
164
+  `(let ((,var (context :session)))
165
+     ,@body))
166
+
115 167
 
116 168
 (def-route "/login" (params)
117
-  (declare (ignore params))
118
-  (let ((state (gen-state 36)))
119
-    (setf (gethash :state *session*) state)
120
-    (multiple-value-bind (content rcode headers) (do-auth-request state)
121
-      (if (< rcode 400)
122
-        `(302 (:location ,(cdr (assoc :location headers))))
123
-        content))))
124
-
125
-(def-route "/oidc_callback" (params)
169
+  (cl-who:with-html-output-to-string (s)
170
+    (:html
171
+      (:head
172
+        (:title "Login"))
173
+      (:body
174
+        (:div (:a :href "/login/facebook" "Facebook"))
175
+        (:div (:a :href "/login/google" "Google")))))) 
176
+
177
+
178
+(def-route "/login/google" (params)
179
+  (with-session (session)
180
+    (let ((state (gen-state 36)))
181
+      (setf (gethash :state session) state)
182
+      (with-goog-endpoints 
183
+        (multiple-value-bind (content rcode headers) (do-auth-request state)
184
+          (if (< rcode 400)
185
+            `(302 (:location ,(cdr (assoc :location headers))))
186
+            content))))))
187
+
188
+
189
+(def-route "/login/facebook" (params)
190
+  (with-session (session)
191
+    (let ((state (gen-state 36)))
192
+      (setf (gethash :state session) state)
193
+      (with-fbook-endpoints 
194
+        (multiple-value-bind (content rcode headers uri) (do-auth-request state)
195
+          (if (< rcode 400)
196
+            `(302 (:location ,(format nil "~a" uri)))
197
+            content))))))
198
+
199
+;(def-route "/oidc_callback" (params)
200
+;  (let ((received-state (cdr (string-assoc "state" params)))
201
+;        (code (cdr (string-assoc "code" params))))
202
+;    (with-fbook-endpoints
203
+;      (check-state received-state
204
+;                   (let* ((a-t (get-access-token code)))
205
+;                     (format nil "~s" a-t))
206
+;                   '(403 '() "Out, vile imposter!")))))
207
+
208
+(def-route "/oidc_callback/google" (params)
126 209
   (let ((received-state (cdr (string-assoc "state" params)))
127 210
         (code (cdr (string-assoc "code" params))))
128 211
     (check-state received-state
129
-                 (let* ((a-t (get-access-token code)) (id-token (assoc-cdr :id--token a-t))
130
-                        (decoded (cljwt:decode id-token :fail-if-unsupported nil)))
131
-                   (setf (gethash :userinfo *session*) decoded)
132
-                   '(302 (:location "/")))
212
+                 (with-session (session)
213
+                   (with-goog-endpoints 
214
+                     (let* ((a-t (get-access-token code)) (id-token (assoc-cdr :id--token a-t))
215
+                            (decoded (cljwt:decode id-token :fail-if-unsupported nil)))
216
+                       (setf (gethash :userinfo session) decoded)
217
+                       '(302 (:location "/")))))
133 218
                  '(403 '() "Out, vile imposter!"))))
134 219
 
220
+
221
+(def-route "/oidc_callback/facebook" (params)
222
+  (let ((received-state (cdr (string-assoc "state" params)))
223
+        (code (cdr (string-assoc "code" params))))
224
+    (with-fbook-endpoints 
225
+      (check-state received-state
226
+                   (with-session (session)
227
+                     (let* ((a-t (get-access-token code))
228
+                            (id-token (assoc-cdr :access--token a-t)))
229
+                       (setf (gethash :userinfo session) (funcall *user-info-cb* id-token))
230
+                       '(302 (:location "/"))))
231
+                   '(403 '() "Out, vile imposter!")))))
232
+
233
+(def-route "/userinfo.json" (params)
234
+  (with-session (session)
235
+    (require-login 
236
+      (with-fbook-endpoints 
237
+        (cl-json:encode-json-to-string (gethash :userinfo session))))))
238
+
239
+(def-route "/logout" (params)
240
+  (with-session (session)
241
+    (setf (gethash :userinfo session) nil)
242
+    '(302 (:location "/"))))
243
+
135 244
 (def-route "/" (params)
136
-  (require-login 
137
-    (anaphora:sunless (gethash :counter *session*) (setf anaphora:it 0))
138
-    (format nil "~Ath visit<br/>~a<br/>~S"
139
-            (incf (gethash :counter *session*))
140
-            *state*
141
-            (alexandria:hash-table-alist *session*))))
245
+  (with-session (session)
246
+    (require-login 
247
+      (anaphora:sunless (gethash :counter session) (setf anaphora:it 0))
248
+      (format nil "~Ath visit<br/>~a<br/><br/>~S<br/>"
249
+              (gethash :counter session)
250
+              (alexandria:hash-table-alist session)
251
+              (alexandria:hash-table-alist (context :session))))))
142 252
 
143 253
 
144 254
 
145
-(setf *handler* (clack:clackup (lack.builder:builder :session *goog-mw* *app*)
146
-                               :port 9090))
255
+(setf *handler* (clack:clackup (lack.builder:builder :session *app*) :port 9090))
147 256
 
148 257
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+{
2
+  "client-id": "<secret>",
3
+  "secret": "<secret>"
4
+}