Browse code
This now authorizes against both Google and Facebook
fiddlerwoaroof authored on 22/08/2015 23:16:49
Showing 3 changed files
Showing 3 changed files
... | ... |
@@ -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 |
|