(defpackage #:cl-openid.example-provider
(:use #:common-lisp #:cl-openid #:puri #:hunchentoot))
(in-package #:cl-openid.example-provider)
(defun html (title body &rest body-args)
"Simple HTML formatting."
(format nil "
~A
~?"
title body body-args))
;;; Store handled requests
(defvar *requests* (make-hash-table :test #'equal)
"Handled requests store.
This hashtable is used to store requests for time of dialogue with
user, between initial checkid_setup request and final decision.")
(defvar *requests-counter* 0
"Counter for generating unique stored request IDs.")
(defun store-request
(message &aux (handle (cl-base64:integer-to-base64-string (incf *requests-counter*) :uri t)))
"Store MESSAGE request in *REQUESTS* under new key, return key."
(setf (gethash handle *requests*) message)
handle)
;;; Actual provider class.
;; To customize OP behaviour and use httpd-specific functions, we need
;; to create subclass of provided abstract OPENID-PROVIDER class.
(defclass example-op (openid-provider)
((finish-uri :initarg :finish-uri :reader finish-uri
:documentation "URI for setup finalization, filled on instance initialization.")))
;; HANDLE-CHECKID-IMMEDIATE method is called on checkid_immediate
;; request. It should examine the request and message, and return
;; whether to accept or reject the request. We try to be funny and to
;; avoid complicating the example too much, and accept every second
;; request.
(defvar *checkid-immediate-counter* 0)
(defmethod handle-checkid-immediate ((op example-op) message)
"Handle checkid_immediate: accept every second request"
(declare (ignore message))
(oddp (incf *checkid-immediate-counter*)))
;; Methods below do reply to OpenID endpoint requests. They should
;; return the same values as HANDLE-OPENID-PROVIDER-REQUEST: reply
;; body and optional HTTP code. If code is a redirect (3xx), body
;; (first returned value) is actually a redirect URI (URI object or
;; string).
;; HANDLE-CHECKID-SETUP method is called on checkid_setup request. It
;; is supposed to handle dialogue with end-user, and is responsible
;; for storing MESSAGE object for time of the dialogue.
(defmethod handle-checkid-setup
((op example-op) message
&aux (handle (store-request message)))
"Response for checkid_setup request.
Presents request details and a simple choice consisting of two links
to FINISH-URI with different parameters."
(values
(html "Log in?"
"Message:
~:{- ~A
- ~A
~}
Log in or cancel?"
(mapcar #'(lambda (c)
(list (car c) (cdr c)))
message)
(copy-uri (finish-uri op) :query (format nil "handle=~A&allow=1" handle))
(copy-uri (finish-uri op) :query (format nil "handle=~A&deny=1" handle)))
200))
;; FINISH-CHECKID-SETUP function is called on request to FINISH-URI,
;; by user clicking one of links presented in response from
;; HANDLE-CHECKID-SETUP. Analyzes request parameters, and returns
;; an relying party URI to redirect the user's browser to
;; (indirect response). The URI parameters tell the relying party
;; if the authentication was successful or not.
(defun finish-checkid-setup (op &aux
(handle (get-parameter "handle"))
(message (gethash handle *requests*))) ; Recover stored message
"Finish checkid setup."
(remhash handle *requests*) ; Message no longer needed
(assert (message-field message "openid.return_to"))
(if (get-parameter "allow") ; Check which of the links was clicked:
(successful-response-uri op message) ; - Allow
(cancel-response-uri op message)) ; - Deny
)
;;; Provider object and Hunchentoot handlers
(defvar *openid-provider* nil
"OpenID Provider object.")
;; Hunchentoot handles
(defun finish-checkid-handle ()
(if (not (gethash (get-parameter "handle") *requests*))
(html "What exactly do you want?"
"~:[ACCESS GRANTED~;ACCESS DENIED~]
But there is no return_to
address, so I can only display this screen to you.
"
(get-parameter "allow"))
(redirect (finish-checkid-setup *openid-provider*))))
(defun provider-ht-handle ()
(multiple-value-bind (body code)
(handle-openid-provider-request *openid-provider*
(append (post-parameters*)
(get-parameters*))
:allow-unencrypted-association-p (ssl-p))
(cond
((<= 300 code 399) ; Redirect, body is actually an URI
(redirect body :code code))
(t (setf (return-code*) code) ; Set return code
body))))
;; Initialization
(defun init-provider (base-uri prefix
&aux
(endpoint-uri (merge-uris prefix base-uri))
(finish-prefix (concatenate 'string prefix "finish-setup"))
(finish-uri (merge-uris finish-prefix base-uri)))
(setf *openid-provider*
(make-instance 'example-op
:endpoint-uri endpoint-uri
:finish-uri finish-uri)
*dispatch-table*
(nconc (list (create-prefix-dispatcher finish-prefix 'finish-checkid-handle)
(create-prefix-dispatcher prefix 'provider-ht-handle))))
;; Without this, Hunchentoot does not allow sending error response
;; body.
(pushnew 400 *approved-return-codes*))
; (init-provider "http://example.com/" "/cl-openid-op/")