diff --git a/lib/camping/session.rb b/lib/camping/session.rb new file mode 100644 index 0000000..731102b --- /dev/null +++ b/lib/camping/session.rb @@ -0,0 +1,70 @@ +# == About camping/session.rb +# +# This file contains two modules which supply basic sessioning to your Camping app. +# Again, we're dealing with a pretty little bit of code: approx. 60 lines. +# +# * Camping::Models::Session is a module which adds a single sessions table +# to your database. +# * Camping::Session is a module which you will mix into your application (or into +# specific controllers which require sessions) to supply a @state variable +# you can use in controllers and views. +# +# For a basic tutorial, see the *Getting Started* section of the Camping::Session module. +#require 'camping' +require 'base64' +require 'digest/sha2' + +module Camping +# The Camping::Session module is designed to be mixed into your application or into specific +# controllers which require sessions. This module defines a service method which +# intercepts all requests handed to those controllers. +# +# == Getting Started +# +# To get sessions working for your application: +# +# 1. require 'camping/session' +# 2. Mixin the module: module YourApp; include Camping::Session end +# 3. Define a secret (and keep it secret): module YourApp; @@state_secret = "SECRET!"; end +# 4. Throughout your application, use the @state var like a hash to store your application's data. +# +# == A Few Notes +# +# * The session is stored in a cookie. Look in @cookies.identity. +# * Session data is only saved if it has changed. +module Session + # This service method, when mixed into controllers, intercepts requests + # and wraps them with code to start and close the session. If a session isn't found + # in the cookie it is created. The @state variable is set and if it changes, + # it is saved back into the cookie. + def service(*a) + if @cookies.identity + blob, secure_hash = @cookies.identity.to_s.split(':', 2) + blob = Base64.decode64(blob) + data = Marshal.restore(blob) + data = {} unless secure_blob_hasher(blob).strip == secure_hash.strip + else + blob = '' + data = {} + end + + app = self.class.name.gsub(/^(\w+)::.+$/, '\1') + @state = (data[app] ||= Camping::H[]) + hash_before = blob.hash + return super(*a) + ensure + data[app] = @state + blob = Marshal.dump(data) + unless hash_before == blob.hash + secure_hash = secure_blob_hasher(blob) + content = Base64.encode64(blob).gsub("\n", '').strip + ':' + secure_hash + raise "The session contains to much data" if content.length > 4096 + @response.set_cookie("identity", content) + end + end + + def secure_blob_hasher(data) + Digest::SHA256.hexdigest(self.class.module_eval('@@state_secret') + data) + end +end +end