Last active
December 21, 2015 20:19
-
-
Save mbj/6360137 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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