git.fiddlerwoaroof.com
Raw Blame History
;;;; cl-oid-connect.lisp
;;;; TODO: Need to refactor out server names!!!

#|
 |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)
; Should this be here?

(defun run-callback-function (endpoint-schema params get-app-user-cb get-login-data)
  (flet ((get-code (params) (assoc-cdr "code" params #'equal)))
    (let ((a-t (get-access-token endpoint-schema (get-code params))))
      (multiple-value-bind (access-token user-info id-token) (funcall get-login-data a-t)
        (auth-callback-skeleton params (:endpoint-schema endpoint-schema
                                        :auth-session-vars (accesstoken userinfo idtoken app-user))
          (setf accesstoken access-token
                userinfo user-info
                idtoken id-token
                app-user (funcall get-app-user-cb user-info id-token access-token)))
        '(302 (:location "/"))))))


(defun load-provider-secrets (provider-info secrets)
  (setf (client-id provider-info) (assoc-cdr :client-id secrets)
        (secret provider-info) (assoc-cdr :secret secrets)))

(defun goog-get-access-token (endpoint-schema code)
  (cl-json:decode-json-from-string
    (drakma:http-request (token-endpoint endpoint-schema)
                         :method :post
                         :redirect nil
                         :parameters `(("code" . ,code)
                                       ("client_id" . ,(client-id endpoint-schema))
                                       ("client_secret" . ,(secret endpoint-schema))
                                       ("redirect_uri" . ,(redirect-uri endpoint-schema))
                                       ("grant_type" . "authorization_code")))))

(defun load-goog-endpoint-schema ()
  (discover-endpoints *goog-endpoint-schema*
                      "https://accounts.google.com/.well-known/openid-configuration"
                      #'goog-get-access-token)
  (setf (redirect-uri *goog-endpoint-schema*) "http://srv2.elangley.org:9090/oidc_callback/google"))

(define-auth-entry-point google-login-entry *goog-endpoint-schema*)
(define-auth-entry-point facebook-login-entry *fbook-endpoint-schema*)

(define-auth-callback google-callback *goog-endpoint-schema* (a-t)
  (labels ((get-real-access-token (a-t) (assoc-cdr :access--token a-t))
           (get-id-token (a-t) (cljwt:decode (assoc-cdr :id--token a-t) :fail-if-unsupported nil)))
    (let ((access-token (get-real-access-token a-t)))
      (values access-token
        (get-user-info *goog-endpoint-schema* access-token)
        (get-id-token a-t)))))

(define-auth-callback facebook-callback *fbook-endpoint-schema* (a-t)
  (labels ((get-id-token (a-t) (assoc-cdr :access--token a-t)))
    ; ^-- access--token is not a mistake here
    (let ((id-token (get-id-token a-t)))
      (values a-t (get-user-info *fbook-endpoint-schema* id-token) id-token))))

(defun initialize-oid-connect (facebook-info google-info)
  "Load the Google and Facebook app secrets and initialize Google's openid-configuration
   form its well-known document"
  (load-provider-secrets *fbook-info* facebook-info)
  (load-provider-secrets *goog-info* google-info) 
  (load-goog-endpoint-schema))

(defun bind-oid-connect-routes (app &optional (login-callback #'identity))
  (setf (route app "/login/google" :method :get) (lambda (params) (google-login-entry params))
        (route app "/login/facebook" :method :get) (lambda (params) (facebook-login-entry params))
        (route app "/oidc_callback/google" :method :get) (google-callback login-callback)
        (route app "/oidc_callback/facebook" :method :get) (facebook-callback login-callback)))