Skip to content

Instantly share code, notes, and snippets.

@mbj
Last active December 21, 2015 20:19
Show Gist options
  • Save mbj/6360137 to your computer and use it in GitHub Desktop.
Save mbj/6360137 to your computer and use it in GitHub Desktop.
module MyApp
# HTTP session
class Session
include Anima.new(:user, :registration)
# Test if session has user
#
# @return [true]
# if session has user
#
# @return [false]
# otherwise
#
# @api private
#
def user?
!!user
end
# Return new empty session
#
# @return [Session]
#
# @api private
#
def self.empty
attributes = anima.attributes.each_with_object({}) do |attribute, attributes|
attributes[attribute.name]=nil
end
new(attributes)
end
# Build new session
#
# @param [Hash] dump
#
# @return [Session]
#
# @api private
#
def self.build(dump)
attributes = { :user => nil, :registration => nil }
if code = dump['user_code']
attributes[:user]= MGC.site.user_by_code(code)
end
if registration = dump['registration']
attributes[:registration] = registration
end
new(attributes)
end
# Set registration
#
# @param [Hash] registration
#
# @return [self]
#
# @api private
#
attr_writer :registration
# Set user
#
# @param [User] user
#
# @return [self]
#
# @api private
#
attr_writer :user
# Return cookie
#
# @return [String]
#
# @api private
#
def cookie
Cookie.new(
:key => 'session',
:domain => nil,
:path => nil,
:expires => nil,
:value => message,
:httponly => true,
:secure => false
)
end
# Return session secret
#
# @return [String]
#
# @api private
#
def self.secret
@secret ||= Digest::SHA256.digest(MGC.config.fetch('session_secret'))
end
# Perform aes operation on message
#
# @param [Symbol] method
# @param [String] message
#
# @return [String]
#
# @api private
#
def self.aes(method, message)
aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc').send(method)
aes.key = secret
aes.update(message) << aes.final
end
private_class_method :aes
# Decrypt message with AES
#
# @param [String] message
#
# @return [String]
#
# @api private
#
def self.decrypt(message)
aes(:decrypt, message)
end
# Encrypt message with AES
#
# @param [String] message
#
# @return [String]
#
# @api private
#
def self.encrypt(message)
aes(:encrypt, message)
end
# Add hmac to message
#
# @param [String] message
#
# @return [String]
#
# @api private
#
def self.hmac(message)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, secret, message)
end
private
# Return message
#
# @return [String]
#
# @api private
#
def message
message = encrypted_message
"#{Base64.urlsafe_encode64(message)}:#{self.class.hmac(encrypted_message)}"
end
# Return encrypted message
#
# @return [String]
#
# @api private
#
def encrypted_message
self.class.encrypt(serialized_message)
end
# Return serialized message
#
# @return [String]
#
# @api private
#
def serialized_message
attributes = {}
attributes['user_code']=user.code if user
attributes['registration']=registration if registration
MultiJson.dump(attributes)
end
class Parser
include Adamantium::Flat
private_class_method :new
# Run session parser
#
# @param [Hash]
#
# @return [Session]
#
# @api private
#
def self.run(*args)
new(*args).session
end
# Return session
#
# @return [Session]
#
# @api private
#
attr_reader :manager
# Return message
#
# @return [Session]
#
# @api private
#
attr_reader :message
# Return session
#
# @return [Session]
#
# @api private
#
def session
return manager.empty unless authorized?
manager.build(deserialized)
end
memoize :session, :freezer => :noop
private
# Initialize object
#
# @param [Session] session
# @param [String] message
#
# @return [undefined]
#
# @api private
#
def initialize(manager, message)
@manager, @message = manager, message
end
# Return deserialized message
#
# @return [Hash]
#
# @api private
#
def deserialized
MultiJson.load(decrypted)
end
# Return decrypted message
#
# @return [String]
#
# @api private
#
def decrypted
manager.decrypt(encrypted)
end
# Test if message is authorized
#
# @return [true]
# if message is authorized
#
# @return [false]
# otherwise
#
# @api private
#
def authorized?
return false unless hmac
manager.hmac(encrypted) == hmac
end
# Return hmac
#
# @return [String]
#
# @api private
#
def hmac
split.last
end
# Return encrypted message
#
# @return [String]
#
# @api private
#
def encrypted
Base64.urlsafe_decode64(split.first)
end
memoize :encrypted
# Return split
#
# @return [Array]
#
# @api private
#
def split
message.split(':', 2)
end
memoize :split
end
# Return session from env
#
# @param [Hash]
# rack env
#
# @return [Session]
#
# @api private
#
def self.parse(env)
return empty
cookie = Rack::Utils.unescape(env.fetch('HTTP_COOKIE'))
key, message = cookie.split('=', 2)
message = '' if key != 'session'
Parser.run(Session, message)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment