e1ac67e4 |
#|
|Copyright (c) 2015 Edward Langley
|All rights reserved.
|
|Redistribution and use in source and binary forms, with or without
|modification, are permitted provided that the following conditions
|are met:
|
|Redistributions of source code must retain the above copyright notice,
|this list of conditions and the following disclaimer.
|
|Redistributions in binary form must reproduce the above copyright
|notice, this list of conditions and the following disclaimer in the
|documentation and/or other materials provided with the distribution.
|
|Neither the name of the project's author nor the names of its
|contributors may be used to endorse or promote products derived from
|this software without specific prior written permission.
|
|THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED
|TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|#
(in-package :cl-oid-connect.utils)
|
03aa798b |
(defparameter *oid* (make-instance '<app>))
(define-condition user-not-logged-in (error) ())
|
e1ac67e4 |
(eval-when (:compile-toplevel :execute :load-toplevel)
(defun vars-to-symbol-macrolets (vars obj)
(iterate:iterate (iterate:for (store key) in (ensure-mapping vars))
(iterate:collect `(,store (gethash ,(alexandria:make-keyword key) ,obj))))))
(defmacro with-session-values (vars session &body body)
(alexandria:once-only (session)
`(symbol-macrolet ,(vars-to-symbol-macrolets vars session)
,@body)))
; This probably should eventually go?
(defmacro with-endpoints (endpoint-schema &body body)
|
03aa798b |
`(let* ((cl-oid-connect.objects::*endpoint-schema* ,endpoint-schema))
|
e1ac67e4 |
,@body))
(defmacro with-session ((var) &body body)
`(progn
(format t "The session var is: ~a it contains: ~a~%" ,(symbol-name var) ,var)
(let ((,var (context :session)))
(format t "The session var is: ~a it now contains: ~a~%" ,(symbol-name var) ,var)
,@body)))
(defmacro def-route ((url args &key (app *oid*) (method :GET)) &body body)
|
03aa798b |
`(setf (route ,app ,url :method ,method)
|
e1ac67e4 |
(lambda ,args
(declare (ignorable ,@args))
,@body)))
|
b8b8a035 |
|
e1ac67e4 |
(defun gen-state (len)
(with-output-to-string (stream)
(let ((*print-base* 36))
(loop repeat len
do (princ (random 36) stream)))))
(defun valid-state (received-state)
(let* ((session (context :session))
(saved-state (gethash :state session)))
(equal saved-state received-state)))
(defmacro my-with-context-variables ((&rest vars) &body body)
"This improves fukamachi's version by permitting the variable to be stored somewhere
besides the symbol corresponding to the keyword."
`(symbol-macrolet
,(loop for (var key) in (ensure-mapping vars)
for form = `(context ,(intern (string key) :keyword))
collect `(,var ,form))
,@body))
(defmacro string-assoc (key alist) `(assoc ,key ,alist :test #'equal))
(defmacro assoc-cdr (key alist &optional (test '#'eql)) `(cdr (assoc ,key ,alist :test ,test)))
(defmacro define-auth-entry-point (name endpoint-schema)
`(defun ,name (params)
(declare (ignore params))
(with-session-values (state endpoint-schema) (context :session)
(setf state (gen-state 36)
endpoint-schema ,endpoint-schema)
(with-endpoints ,endpoint-schema
|
03aa798b |
(multiple-value-bind (content rcode headers uri) (cl-oid-connect.objects:do-auth-request ,endpoint-schema state)
|
e1ac67e4 |
(declare (ignore headers))
(if (< rcode 400) `(302 (:location ,(format nil "~a" uri)))
content))))))
(defmacro define-auth-callback (name endpoint-schema params &body body)
(with-gensyms (get-app-user-cb cb-params)
`(defun ,name (,get-app-user-cb)
(lambda (,cb-params)
|
03aa798b |
(cl-oid-connect:run-callback-function
|
e1ac67e4 |
,endpoint-schema ,cb-params ,get-app-user-cb
(lambda ,params
,@body))))))
(defmacro reject-when-state-invalid (params &body body)
(alexandria:with-gensyms (received-state)
(alexandria:once-only (params)
`(let ((,received-state (cdr (string-assoc "state" ,params))))
(if (not (valid-state ,received-state))
'(403 '() "Out, vile imposter!")
,@body)))))
(defmacro auth-callback-skeleton (params (&key endpoint-schema auth-session-vars) &body body)
(alexandria:with-gensyms (session)
(alexandria:once-only (params endpoint-schema)
`(reject-when-state-invalid ,params
(with-endpoints ,endpoint-schema
(my-with-context-variables ((,session session))
,(if (null auth-session-vars)
`(progn
,@body)
`(with-session-values ,auth-session-vars ,session
,@body))))))))
(defmacro ensure-logged-in (&body body)
"Ensure that the user is logged in: otherwise throw the condition user-not-logged-in"
(alexandria:with-gensyms (session userinfo)
`(my-with-context-variables ((,session session))
(with-session-values ((,userinfo userinfo)) ,session
|
b8b8a035 |
(if ,userinfo
(progn ,@body)
(progn
|
e1ac67e4 |
(setf ,userinfo nil)
|
b8b8a035 |
(format t "Clearing all the infos")
(error 'user-not-logged-in)))))))
|
e1ac67e4 |
(defmacro setup-oid-connect (app args &body callback)
|
03aa798b |
`(cl-oid-connect::bind-oid-connect-routes ,app (lambda ,args ,@callback)))
|
e1ac67e4 |
|
b8b8a035 |
(defun save-redirect (path)
(with-session-values (next-page) (context :session)
(setf next-page path)))
(defun call-with-login (authorized-cb unauthorized-cb)
(handler-case
(ensure-logged-in
(funcall authorized-cb))
(user-not-logged-in (c)
(funcall unauthorized-cb c))))
(defmacro with-login (handler (sub (sym) &body unauthorized-action))
(unless (eq sub :unauthorized)
(error 'error "unauthorized clause must start with \"unauthorized\""))
`(call-with-login
(lambda ()
,handler)
(lambda (,sym)
,@unauthorized-action)))
|
e1ac67e4 |
(flet ((handle-no-user (main-body handler-body)
`(handler-case (ensure-logged-in ,@main-body)
(user-not-logged-in (e) (declare (ignorable e))
,@handler-body))))
(defmacro check-login (&body body)
"Returns an HTTP 401 Error if not logged in."
(handle-no-user body `('(401 () "Unauthorized"))))
(defmacro require-login (&body body)
"Redirects to /login if not logged in."
|
b8b8a035 |
(handle-no-user
body
`((save-redirect (lack.request:request-path-info *request*))
'(302 (:location "/login"))))))
|
e1ac67e4 |
(defmacro redirect-if-necessary (sessionvar &body body)
(with-gensyms (session)
`(let* ((,session ,sessionvar)
(next-page (gethash :next-page ,session)))
(if (and (not (null next-page))
(not (string= next-page (lack.request:request-path-info *request*))))
(progn
(setf (gethash :next-page ,session) nil)
|
03aa798b |
`(302 (:location ,next-page)))
(progn
,@body)))))
|
89bed873 |
|