working
commit
e89a257a5c
@ -0,0 +1 @@
|
||||
This is the stub README.txt for the "hunchentoot-postmodern-sessions" project.
|
@ -0,0 +1,362 @@
|
||||
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: HUNCHENTOOT; Base: 10 -*-
|
||||
|
||||
;;; Copyright (c) 2004-2010, Dr. Edmund Weitz. 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.
|
||||
|
||||
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
|
||||
;;; 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 AUTHOR 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 :hunchentoot)
|
||||
|
||||
(defgeneric session-db-lock (acceptor &key whole-db-p)
|
||||
(:documentation "A function which returns a lock that will be used
|
||||
to prevent concurrent access to sessions. The first argument will be
|
||||
the acceptor that handles the current request, the second argument is
|
||||
true if the whole \(current) session database is modified. If it is
|
||||
NIL, only one existing session in the database is modified.
|
||||
|
||||
This function can return NIL which means that sessions or session
|
||||
databases will be modified without a lock held \(for example for
|
||||
single-threaded environments). The default is to always return a
|
||||
global lock \(ignoring the ACCEPTOR argument) for Lisps that support
|
||||
threads and NIL otherwise."))
|
||||
|
||||
(defmethod session-db-lock ((acceptor t) &key (whole-db-p t))
|
||||
(declare (ignore whole-db-p))
|
||||
*global-session-db-lock*)
|
||||
|
||||
(defmacro with-session-lock-held ((lock) &body body)
|
||||
"This is like WITH-LOCK-HELD except that it will accept NIL as a
|
||||
\"lock\" and just execute BODY in this case."
|
||||
(with-unique-names (thunk)
|
||||
(with-rebinding (lock)
|
||||
`(flet ((,thunk () ,@body))
|
||||
(cond (,lock (with-lock-held (,lock) (,thunk)))
|
||||
(t (,thunk)))))))
|
||||
|
||||
(defgeneric session-db (acceptor)
|
||||
(:documentation "Returns the current session database which is an
|
||||
alist where each car is a session's ID and the cdr is the
|
||||
corresponding SESSION object itself. The default is to use a global
|
||||
list for all acceptors."))
|
||||
|
||||
(defmethod session-db ((acceptor t))
|
||||
*session-db*)
|
||||
|
||||
(defgeneric (setf session-db) (new-value acceptor)
|
||||
(:documentation "Modifies the current session database. See SESSION-DB."))
|
||||
|
||||
(defmethod (setf session-db) (new-value (acceptor t))
|
||||
(setq *session-db* new-value))
|
||||
|
||||
(defgeneric next-session-id (acceptor)
|
||||
(:documentation "Returns the next sequential session ID, an integer,
|
||||
which should be unique per session. The default method uses a simple
|
||||
global counter and isn't guarded by a lock. For a high-performance
|
||||
production environment you might consider using a more robust
|
||||
implementation."))
|
||||
|
||||
(let ((session-id-counter 0))
|
||||
(defmethod next-session-id ((acceptor t))
|
||||
(incf session-id-counter)))
|
||||
|
||||
(defclass session ()
|
||||
((session-id :initform (next-session-id (request-acceptor *request*))
|
||||
:reader session-id
|
||||
:type integer
|
||||
:documentation "The unique ID \(an INTEGER) of the session.")
|
||||
(session-string :reader session-string
|
||||
:documentation "The session string encodes enough
|
||||
data to safely retrieve this session. It is sent to the browser as a
|
||||
cookie value or as a GET parameter.")
|
||||
(user-agent :initform (user-agent *request*)
|
||||
:reader session-user-agent
|
||||
:documentation "The incoming 'User-Agent' header that
|
||||
was sent when this session was created.")
|
||||
(remote-addr :initform (real-remote-addr *request*)
|
||||
:reader session-remote-addr
|
||||
:documentation "The remote IP address of the client
|
||||
when this session was started as returned by REAL-REMOTE-ADDR.")
|
||||
(session-start :initform (get-universal-time)
|
||||
:reader session-start
|
||||
:documentation "The time this session was started.")
|
||||
(last-click :initform (get-universal-time)
|
||||
:reader session-last-click
|
||||
:documentation "The last time this session was used.")
|
||||
(session-data :initarg :session-data
|
||||
:initform nil
|
||||
:reader session-data
|
||||
:documentation "Data associated with this session -
|
||||
see SESSION-VALUE.")
|
||||
(max-time :initarg :max-time
|
||||
:initform *session-max-time*
|
||||
:accessor session-max-time
|
||||
:type fixnum
|
||||
:documentation "The time \(in seconds) after which this
|
||||
session expires if it's not used."))
|
||||
(:documentation "SESSION objects are automatically maintained by
|
||||
Hunchentoot. They should not be created explicitly with MAKE-INSTANCE
|
||||
but implicitly with START-SESSION and they should be treated as opaque
|
||||
objects.
|
||||
|
||||
You can ignore Hunchentoot's SESSION objects altogether and implement
|
||||
your own sessions if you provide corresponding methods for
|
||||
SESSION-COOKIE-VALUE and SESSION-VERIFY."))
|
||||
|
||||
(defun encode-session-string (id user-agent remote-addr start)
|
||||
"Creates a uniquely encoded session string based on the values ID,
|
||||
USER-AGENT, REMOTE-ADDR, and START"
|
||||
(unless (boundp '*session-secret*)
|
||||
(hunchentoot-warn "Session secret is unbound. Using Lisp's RANDOM function to initialize it.")
|
||||
(reset-session-secret))
|
||||
;; *SESSION-SECRET* is used twice due to known theoretical
|
||||
;; vulnerabilities of MD5 encoding
|
||||
(md5-hex (concatenate 'string
|
||||
*session-secret*
|
||||
(md5-hex (format nil "~A~A~@[~A~]~@[~A~]~A"
|
||||
*session-secret*
|
||||
id
|
||||
(and *use-user-agent-for-sessions*
|
||||
user-agent)
|
||||
(and *use-remote-addr-for-sessions*
|
||||
remote-addr)
|
||||
start)))))
|
||||
|
||||
(defun stringify-session (session)
|
||||
"Creates a string representing the SESSION object SESSION. See
|
||||
ENCODE-SESSION-STRING."
|
||||
(encode-session-string (session-id session)
|
||||
(session-user-agent session)
|
||||
(session-remote-addr session)
|
||||
(session-start session)))
|
||||
|
||||
(defmethod initialize-instance :after ((session session) &rest init-args)
|
||||
"Set SESSION-STRING slot after the session has been initialized."
|
||||
(declare (ignore init-args))
|
||||
(setf (slot-value session 'session-string) (stringify-session session)))
|
||||
|
||||
(defun session-gc ()
|
||||
"Removes sessions from the current session database which are too
|
||||
old - see SESSION-TOO-OLD-P."
|
||||
(with-session-lock-held ((session-db-lock *acceptor*))
|
||||
(setf (session-db *acceptor*)
|
||||
(loop for id-session-pair in (session-db *acceptor*)
|
||||
for (nil . session) = id-session-pair
|
||||
when (session-too-old-p session)
|
||||
do (acceptor-remove-session *acceptor* session)
|
||||
else
|
||||
collect id-session-pair)))
|
||||
(values))
|
||||
|
||||
(defun session-value (symbol &optional (session *session*))
|
||||
"Returns the value associated with SYMBOL from the session object
|
||||
SESSION \(the default is the current session) if it exists."
|
||||
(when session
|
||||
(let ((found (assoc symbol (session-data session) :test #'eq)))
|
||||
(values (cdr found) found))))
|
||||
|
||||
(defsetf session-value (symbol &optional session)
|
||||
(new-value)
|
||||
"Sets the value associated with SYMBOL from the session object
|
||||
SESSION. If there is already a value associated with SYMBOL it will be
|
||||
replaced. Will automatically start a session if none was supplied and
|
||||
there's no session for the current request."
|
||||
(with-rebinding (symbol)
|
||||
(with-unique-names (place %session)
|
||||
`(let ((,%session (or ,session (start-session))))
|
||||
(with-session-lock-held ((session-db-lock *acceptor* :whole-db-p nil))
|
||||
(let* ((,place (assoc ,symbol (session-data ,%session) :test #'eq)))
|
||||
(cond
|
||||
(,place
|
||||
(setf (cdr ,place) ,new-value))
|
||||
(t
|
||||
(push (cons ,symbol ,new-value)
|
||||
(slot-value ,%session 'session-data))
|
||||
,new-value))))))))
|
||||
|
||||
(defun delete-session-value (symbol &optional (session *session*))
|
||||
"Removes the value associated with SYMBOL from SESSION if there is
|
||||
one."
|
||||
(when session
|
||||
(setf (slot-value session 'session-data)
|
||||
(delete symbol (session-data session)
|
||||
:key #'car :test #'eq)))
|
||||
(values))
|
||||
|
||||
(defgeneric session-cookie-value (session)
|
||||
(:documentation "Returns a string which can be used to safely
|
||||
restore the session SESSION if as session has already been
|
||||
established. This is used as the value stored in the session cookie
|
||||
or in the corresponding GET parameter and verified by SESSION-VERIFY.
|
||||
|
||||
A default method is provided and there's no reason to change it unless
|
||||
you want to use your own session objects."))
|
||||
|
||||
(defmethod session-cookie-value ((session session))
|
||||
(and session
|
||||
(format nil
|
||||
"~D:~A"
|
||||
(session-id session)
|
||||
(session-string session))))
|
||||
|
||||
(defgeneric session-cookie-name (acceptor)
|
||||
(:documentation "Returns the name \(a string) of the cookie \(or the
|
||||
GET parameter) which is used to store a session on the client side.
|
||||
The default is to use the string \"hunchentoot-session\", but you can
|
||||
specialize this function if you want another name."))
|
||||
|
||||
(defmethod session-cookie-name ((acceptor t))
|
||||
"hunchentoot-session")
|
||||
|
||||
(defgeneric session-created (acceptor new-session)
|
||||
(:documentation "This function is called whenever a new session has
|
||||
been created. There's a default method which might trigger a session
|
||||
GC based on the value of *SESSION-GC-FREQUENCY*.
|
||||
|
||||
The return value is ignored."))
|
||||
|
||||
(let ((global-session-usage-counter 0))
|
||||
(defmethod session-created ((acceptor t) (session t))
|
||||
"Counts session usage globally and triggers session GC if
|
||||
necessary."
|
||||
(when (and *session-gc-frequency*
|
||||
(Zerop (mod (incf global-session-usage-counter)
|
||||
*session-gc-frequency*)))
|
||||
(session-gc))))
|
||||
|
||||
(defun start-session ()
|
||||
"Returns the current SESSION object. If there is no current session,
|
||||
creates one and updates the corresponding data structures. In this
|
||||
case the function will also send a session cookie to the browser."
|
||||
(let ((session (session *request*)))
|
||||
(when session
|
||||
(return-from start-session session))
|
||||
(setf session (make-instance 'session)
|
||||
(session *request*) session)
|
||||
(with-session-lock-held ((session-db-lock *acceptor*))
|
||||
(setf (session-db *acceptor*)
|
||||
(acons (session-id session) session (session-db *acceptor*))))
|
||||
(set-cookie (session-cookie-name *acceptor*)
|
||||
:value (session-cookie-value session)
|
||||
:path "/")
|
||||
(session-created *acceptor* session)
|
||||
(setq *session* session)))
|
||||
|
||||
(defun remove-session (session)
|
||||
"Completely removes the SESSION object SESSION from Hunchentoot's
|
||||
internal session database."
|
||||
(with-session-lock-held ((session-db-lock *acceptor*))
|
||||
(acceptor-remove-session *acceptor* session)
|
||||
(setf (session-db *acceptor*)
|
||||
(delete (session-id session) (session-db *acceptor*)
|
||||
:key #'car :test #'=)))
|
||||
(values))
|
||||
|
||||
(defun session-too-old-p (session)
|
||||
"Returns true if the SESSION object SESSION has not been active in
|
||||
the last \(SESSION-MAX-TIME SESSION) seconds."
|
||||
(< (+ (session-last-click session) (session-max-time session))
|
||||
(get-universal-time)))
|
||||
|
||||
(defun get-stored-session (id)
|
||||
"Returns the SESSION object corresponding to the number ID if the
|
||||
session has not expired. Will remove the session if it has expired but
|
||||
will not create a new one."
|
||||
(let ((session
|
||||
(cdr (assoc id (session-db *acceptor*) :test #'=))))
|
||||
(when (and session
|
||||
(session-too-old-p session))
|
||||
(when *reply*
|
||||
(log-message* :info "Session with ID ~A too old" id))
|
||||
(remove-session session)
|
||||
(setq session nil))
|
||||
session))
|
||||
|
||||
(defgeneric session-verify (request)
|
||||
(:documentation "Tries to get a session identifier from the cookies
|
||||
\(or alternatively from the GET parameters) sent by the client (see
|
||||
SESSION-COOKIE-NAME and SESSION-COOKIE-VALUE). This identifier is
|
||||
then checked for validity against the REQUEST object REQUEST. On
|
||||
success the corresponding session object \(if not too old) is returned
|
||||
\(and updated). Otherwise NIL is returned.
|
||||
|
||||
A default method is provided and you only need to write your own one
|
||||
if you want to maintain your own sessions."))
|
||||
|
||||
(defmethod session-verify ((request request))
|
||||
(let ((session-identifier (or (when-let (session-cookie (cookie-in (session-cookie-name *acceptor*) request))
|
||||
(url-decode session-cookie))
|
||||
(get-parameter (session-cookie-name *acceptor*) request))))
|
||||
(unless (and session-identifier
|
||||
(stringp session-identifier)
|
||||
(plusp (length session-identifier)))
|
||||
(return-from session-verify nil))
|
||||
(destructuring-bind (id-string session-string)
|
||||
(split ":" session-identifier :limit 2)
|
||||
(let* ((id (parse-integer id-string))
|
||||
(session (get-stored-session id))
|
||||
(user-agent (user-agent request))
|
||||
(remote-addr (remote-addr request)))
|
||||
(cond
|
||||
((and session
|
||||
(string= session-string
|
||||
(session-string session))
|
||||
(string= session-string
|
||||
(encode-session-string id
|
||||
user-agent
|
||||
(real-remote-addr request)
|
||||
(session-start session))))
|
||||
;; the session key presented by the client is valid
|
||||
(setf (slot-value session 'last-click) (get-universal-time))
|
||||
session)
|
||||
(session
|
||||
;; the session ID pointed to an existing session, but the
|
||||
;; session string did not match the expected session string
|
||||
(log-message* :warning
|
||||
"Fake session identifier '~A' (User-Agent: '~A', IP: '~A')"
|
||||
session-identifier user-agent remote-addr)
|
||||
;; remove the session to make sure that it can't be used
|
||||
;; again; the original legitimate user will be required to
|
||||
;; log in again
|
||||
(remove-session session)
|
||||
nil)
|
||||
(t
|
||||
;; no session was found under the ID given, presumably
|
||||
;; because it has expired.
|
||||
(log-message* :info
|
||||
"No session for session identifier '~A' (User-Agent: '~A', IP: '~A')"
|
||||
session-identifier user-agent remote-addr)
|
||||
nil))))))
|
||||
|
||||
(defun reset-session-secret ()
|
||||
"Sets *SESSION-SECRET* to a new random value. All old sessions will
|
||||
cease to be valid."
|
||||
(setq *session-secret* (create-random-string 10 36)))
|
||||
|
||||
(defun reset-sessions (&optional (acceptor *acceptor*))
|
||||
"Removes ALL stored sessions of ACCEPTOR."
|
||||
(with-session-lock-held ((session-db-lock acceptor))
|
||||
(loop for (nil . session) in (session-db acceptor)
|
||||
do (acceptor-remove-session acceptor session))
|
||||
(setq *session-db* nil))
|
||||
(values))
|
@ -0,0 +1,8 @@
|
||||
(defpackage #:pg-sessions
|
||||
(:use #:cl #:postmodern #:anaphora #:alexandria)
|
||||
(:export #:pg-session
|
||||
#:pg-session-id
|
||||
#:pg-session-gc
|
||||
#:pg-start-session
|
||||
#:save-pg-session))
|
||||
|
@ -0,0 +1,13 @@
|
||||
(asdf:defsystem #:pg-sessions
|
||||
:serial t
|
||||
:description "Describe hunchentoot-postmodern-sessions here"
|
||||
:author "Your Name <your.name@example.com>"
|
||||
:license "Specify license here"
|
||||
:depends-on (#:postmodern
|
||||
#:anaphora
|
||||
#:alexandria
|
||||
#:cl-ppcre
|
||||
#:hunchentoot)
|
||||
:components ((:file "package")
|
||||
(:file "pg-sessions")))
|
||||
|
@ -0,0 +1,227 @@
|
||||
;;; Copyright (c) 2004-2010, Dr. Edmund Weitz. All rights reserved.
|
||||
;;; Copyright (c) 2014, Thomas Hintz. 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.
|
||||
|
||||
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
|
||||
;;; 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 AUTHOR 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 #:pg-sessions)
|
||||
|
||||
(defparameter *hunchentoot-postmodern-session-max-time* 3600) ; 1 hour
|
||||
(defvar *pg-session-secret* "thisisasecret")
|
||||
|
||||
(defclass pg-session ()
|
||||
((session-id :col-type serial :initarg :session-id :reader pg-session-id)
|
||||
(session-string :col-type string :initform "" :accessor pg-session-string
|
||||
:documentation "The session string encodes enough
|
||||
data to safely retrieve this session. It is sent to the browser as a
|
||||
cookie value or as a GET parameter.")
|
||||
(user-agent :initarg :user-agent
|
||||
:accessor pg-session-user-agent)
|
||||
(remote-addr :initarg :remote-addr
|
||||
:accessor pg-session-remote-addr)
|
||||
(session-start :col-type bigint :initform (get-universal-time) :accessor pg-session-start)
|
||||
(last-click :col-type bigint :initform (get-universal-time) :accessor pg-session-last-click
|
||||
:documentation "The last time this session was used.")
|
||||
(max-time :col-type bigint :initarg :max-time
|
||||
:initform *hunchentoot-postmodern-session-max-time*
|
||||
:accessor pg-session-max-time
|
||||
:documentation "The time \(in seconds) after which this
|
||||
session expires if it's not used."))
|
||||
(:metaclass dao-class)
|
||||
(:keys session-id)
|
||||
(:documentation "An extensions of hunchentoot's sessions so that sessions can be
|
||||
stored in the postmodern database."))
|
||||
|
||||
(defun md5-hex (string)
|
||||
"Calculates the md5 sum of the string STRING and returns it as a hex string."
|
||||
(with-output-to-string (s)
|
||||
(loop for code across (md5:md5sum-string string)
|
||||
do (format s "~2,'0x" code))))
|
||||
|
||||
(defun encode-session-string (id user-agent remote-addr start)
|
||||
"Creates a uniquely encoded session string based on the values ID,
|
||||
USER-AGENT, and START"
|
||||
;; *PG-SESSION-SECRET* is used twice due to known theoretical
|
||||
;; vulnerabilities of MD5 encoding
|
||||
(hunchentoot:log-message* :info "UA: ~A" user-agent)
|
||||
(hunchentoot:log-message* :info "addr: ~A" remote-addr)
|
||||
(hunchentoot:log-message* :info "start: ~A" start)
|
||||
(hunchentoot:log-message* :info "id: ~A" id)
|
||||
(let ((r (md5-hex (concatenate 'string
|
||||
*pg-session-secret*
|
||||
(md5-hex (format nil "~A~A~@[~A~]~@[~A~]~A"
|
||||
*pg-session-secret*
|
||||
id
|
||||
(and hunchentoot:*use-user-agent-for-sessions*
|
||||
user-agent)
|
||||
(and hunchentoot:*use-remote-addr-for-sessions*
|
||||
remote-addr)
|
||||
start))))))
|
||||
(hunchentoot:log-message* :info "encoding: ~A" r)
|
||||
r))
|
||||
|
||||
(defun stringify-session (session)
|
||||
"Creates a string representing of the SESSION object SESSION. See
|
||||
ENCODE-SESSION-STRING."
|
||||
(hunchentoot:log-message* :info "UA: ~A" (pg-session-user-agent session))
|
||||
(hunchentoot:log-message* :info "addr: ~A" (pg-session-remote-addr session))
|
||||
(hunchentoot:log-message* :info "start: ~A" (pg-session-start session))
|
||||
(encode-session-string (pg-session-id session)
|
||||
(pg-session-user-agent session)
|
||||
(pg-session-remote-addr session)
|
||||
(pg-session-start session)))
|
||||
|
||||
(defun session-too-old-p (session)
|
||||
(< (+ (pg-session-last-click session) (pg-session-max-time session))
|
||||
(get-universal-time)))
|
||||
|
||||
(defun pg-remove-session (session)
|
||||
"Completely removes the SESSION object SESSION from Hunchentoot's
|
||||
internal session database."
|
||||
(delete-dao session)
|
||||
(values))
|
||||
|
||||
(defun pg-session-gc ()
|
||||
"Removes sessions from the current session database which are too
|
||||
old - see SESSION-TOO-OLD-P."
|
||||
(loop for session in (select-dao 'pg-session)
|
||||
when (session-too-old-p session)
|
||||
do (remove-session session)))
|
||||
|
||||
(defmethod hunchentoot:session-cookie-value ((pg-session pg-session))
|
||||
(hunchentoot:log-message* :warning "session verify")
|
||||
(and pg-session
|
||||
(format nil
|
||||
"~D:~A"
|
||||
(pg-session-id pg-session)
|
||||
(pg-session-string pg-session))))
|
||||
|
||||
(defun save-pg-session (session)
|
||||
(upsert-dao session))
|
||||
|
||||
(defun pg-start-session ()
|
||||
"Returns the current SESSION object. If there is no current session,
|
||||
creates one and updates the corresponding data structures. In this
|
||||
case the function will also send a session cookie to the browser."
|
||||
(hunchentoot:log-message* :info "in pg-start-session")
|
||||
(let ((session (hunchentoot:session hunchentoot:*request*)))
|
||||
(when session
|
||||
(return-from pg-start-session session))
|
||||
(hunchentoot:log-message* :info "session before")
|
||||
(setf session (make-dao 'pg-session
|
||||
:user-agent (hunchentoot:user-agent hunchentoot:*request*)
|
||||
:remote-addr (hunchentoot:real-remote-addr hunchentoot:*request*)))
|
||||
(hunchentoot:log-message* :info "session ~S" session)
|
||||
(hunchentoot:log-message* :info "session-id: ~A" (pg-session-id session))
|
||||
(setf (hunchentoot:session hunchentoot:*request*) session)
|
||||
(setf (pg-session-string session) (stringify-session session))
|
||||
(save-pg-session session)
|
||||
(hunchentoot:set-cookie (hunchentoot:session-cookie-name hunchentoot:*acceptor*)
|
||||
:value (hunchentoot:session-cookie-value session)
|
||||
:path "/")
|
||||
(setq hunchentoot:*session* session)))
|
||||
|
||||
(defun get-stored-session (id request)
|
||||
"Returns the SESSION object corresponding to the number ID if the
|
||||
session has not expired. Will remove the session if it has expired but
|
||||
will not create a new one."
|
||||
(hunchentoot:log-message* :info "get-stored-session ~A" id)
|
||||
(hunchentoot:log-message* :info "request ~S" request)
|
||||
(let ((session (get-dao 'pg-session id)))
|
||||
(hunchentoot:log-message* :info "get-stored-session ~A recieved" id)
|
||||
(hunchentoot:log-message* :info "session: ~A" session)
|
||||
(when (and session
|
||||
(session-too-old-p session))
|
||||
(when hunchentoot:*reply*
|
||||
(hunchentoot:log-message* :info "Session with ID ~A too old" id))
|
||||
(pg-remove-session session)
|
||||
(setq session nil))
|
||||
(when session
|
||||
(hunchentoot:log-message* :info "setting stuff")
|
||||
(setf (pg-session-user-agent session) (hunchentoot:user-agent request)
|
||||
(pg-session-remote-addr session) (hunchentoot:real-remote-addr request)))
|
||||
session))
|
||||
|
||||
(defmethod hunchentoot:session-verify ((request hunchentoot:request))
|
||||
(hunchentoot:log-message* :info "session verify")
|
||||
(hunchentoot:log-message* :warning "UNCOMMENT remove session in faked")
|
||||
(with-connection
|
||||
'("foods" "tjhintz" "" "localhost" :pooled-p t)
|
||||
(with-transaction (transaction)
|
||||
(let ((session-identifier
|
||||
(or (when-let (session-cookie (hunchentoot:cookie-in
|
||||
(hunchentoot:session-cookie-name
|
||||
hunchentoot:*acceptor*) request))
|
||||
(hunchentoot:url-decode session-cookie))
|
||||
(hunchentoot:get-parameter (hunchentoot:session-cookie-name
|
||||
hunchentoot:*acceptor*) request))))
|
||||
(unless (and session-identifier
|
||||
(stringp session-identifier)
|
||||
(plusp (length session-identifier)))
|
||||
(return-from session-verify nil))
|
||||
(destructuring-bind (id-string session-string)
|
||||
(cl-ppcre:split ":" session-identifier :limit 2)
|
||||
(let* ((id (parse-integer id-string))
|
||||
(session (get-stored-session id request))
|
||||
(user-agent (hunchentoot:user-agent request))
|
||||
(remote-addr (hunchentoot:remote-addr request)))
|
||||
(cond
|
||||
((and session
|
||||
(string= session-string
|
||||
(pg-session-string session))
|
||||
(string= session-string
|
||||
(encode-session-string id
|
||||
user-agent
|
||||
(hunchentoot:real-remote-addr request)
|
||||
(pg-session-start session))))
|
||||
;; the session key presented by the client is valid
|
||||
(setf (pg-session-last-click session) (get-universal-time))
|
||||
(save-pg-session session)
|
||||
session)
|
||||
(session
|
||||
;; the session ID pointed to an existing session, but the
|
||||
;; session string did not match the expected session string
|
||||
(hunchentoot:log-message*
|
||||
:warning "Fake session identifier '~A' (User-Agent: '~A', IP: '~A')"
|
||||
session-identifier user-agent remote-addr)
|
||||
;; remove the session to make sure that it can't be used
|
||||
;; again; the original legitimate user will be required to
|
||||
;; log in again
|
||||
;; (pg-remove-session session)
|
||||
nil)
|
||||
(t
|
||||
;; no session was found under the ID given, presumably
|
||||
;; because it has expired.
|
||||
(hunchentoot:log-message*
|
||||
:info "No session for session identifier '~A' (User-Agent: '~A', IP: '~A')"
|
||||
session-identifier user-agent remote-addr)
|
||||
nil))))))))
|
||||
|
||||
;; (defun reset-sessions (&optional (acceptor *acceptor*))
|
||||
;; "Removes ALL stored sessions of ACCEPTOR."
|
||||
;; (with-session-lock-held ((session-db-lock acceptor))
|
||||
;; (loop for (nil . session) in (session-db acceptor)
|
||||
;; do (acceptor-remove-session acceptor session))
|
||||
;; (setq *session-db* nil))
|
||||
;; (values))
|
Loading…
Reference in New Issue