Class: SimpleSession::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/simple_session/base.rb

Direct Known Subclasses

Session

Defined Under Namespace

Classes: OptionHash

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ Base

Returns a new instance of Base.



11
12
13
14
15
16
17
# File 'lib/simple_session/base.rb', line 11

def initialize app, options = {}
  @app          = app
  @key          = options.fetch :key, 'rack.session'
  @secret       = options.fetch :secret, get_secret_errors(options)
  @options_key  = options.fetch :options_key, 'rack.session.options'
  @default_opts = DEFAULT_OPTS.merge!(options)
end

Instance Attribute Details

#requestObject (readonly)

Returns the value of attribute request.



9
10
11
# File 'lib/simple_session/base.rb', line 9

def request
  @request
end

#sessionObject (readonly)

Returns the value of attribute session.



9
10
11
# File 'lib/simple_session/base.rb', line 9

def session
  @session
end

Instance Method Details

#add_session(headers) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/simple_session/base.rb', line 87

def add_session headers
  cookie = Hash.new
  cookie.merge!(session.fetch(:options, @default_opts))
  cookie[:value] = encrypt session

  set_cookie_header headers, @key, cookie
end

#call(env) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/simple_session/base.rb', line 43

def call env
  # Decrypt request session and store it 
  extract_session env

  # Load session into app env
  load_environment env

  # Pass on request
  status, headers, body = @app.call env

  # Check session for changes and update
  update_options if options_changed?
  update_session if session_changed?

  # Encrypt and add session to headers
  add_session headers

  [status, headers, body]
end

#cipher_keyObject



181
182
183
# File 'lib/simple_session/base.rb', line 181

def cipher_key
  hmac("A red, red fox has had three socks but all have holes" + @secret)
end

#decrypt(data) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/simple_session/base.rb', line 133

def decrypt data
  # Decode Base64
  b = data.unpack('m0').first

  # Parse Signature
  h1   = b[0, 32]
  data = b[32..-1]
  h2   = data.reverse[0, 32].reverse
  data = data.reverse[32..-1].reverse

  if h1 == signature.to_s && h2 == signature.to_s
    # Decipher
    Marshal.load unload_cipher data
  else
    raise SecurityError
  end
end

#digestObject



151
152
153
# File 'lib/simple_session/base.rb', line 151

def digest
  OpenSSL::Digest.new 'sha256', @secret
end

#encrypt(data) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/simple_session/base.rb', line 119

def encrypt data
  # Serialize
  m = Marshal.dump data

  # Cipher
  c = load_cipher m

  # Sign
  h = signature + c + signature

  # Encode Base64
  [h].pack('m0')
end

#extract_session(env) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/simple_session/base.rb', line 63

def extract_session env
  begin
    @request = Rack::Request.new env
    @session = req_session ? decrypt(req_session) : new_session_hash
    session.merge!(options_hash)
    raise ArgumentError, "Unable to decrypt session" unless session

  rescue Exception => e
    @session = new_session_hash.merge!(options_hash)
    print e.message
  end

end

#get_secret_errors(options) ⇒ Object

Raises:

  • (ArgumentError)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/simple_session/base.rb', line 19

def get_secret_errors options
  secret = options[:secret]
  missing_msg = %[
    SimpleSession requires a secret like this:
    use SimpleSession::Session, secret: 'some secret'
  ]

  short_msg  = %[
    SimpleSession require a secret with a minimum length of 32
    use SimpleSession::Session, secret: SecureRandom.hex(32)
  ]

  raise ArgumentError, missing_msg unless secret
  raise ArgumentError, short_msg   unless secret.length >= 32
end

#hmac(data) ⇒ Object



155
156
157
# File 'lib/simple_session/base.rb', line 155

def hmac data
  OpenSSL::HMAC.digest digest, @secret, data
end

#load_cipher(marshaled_data) ⇒ Object



163
164
165
166
167
168
169
170
171
# File 'lib/simple_session/base.rb', line 163

def load_cipher marshaled_data
  cipher = new_cipher
  cipher.encrypt
  iv = cipher.random_iv
  cipher.iv  = iv
  cipher.key = cipher_key

  iv + cipher.update(marshaled_data) + cipher.final
end

#load_environment(env) ⇒ Object



82
83
84
85
# File 'lib/simple_session/base.rb', line 82

def load_environment env
  env[@key] = session.dup
  env[@options_key] = session[:options].dup
end

#new_cipherObject



159
160
161
# File 'lib/simple_session/base.rb', line 159

def new_cipher
  OpenSSL::Cipher::AES.new(256, :CBC)
end

#new_session_hashObject



39
40
41
# File 'lib/simple_session/base.rb', line 39

def new_session_hash
  { session_id: SecureRandom.hex(32) }
end

#options_changed?Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/simple_session/base.rb', line 103

def options_changed? 
  request.session_options != session[:options]
end

#options_hashObject



77
78
79
80
# File 'lib/simple_session/base.rb', line 77

def options_hash
  o = session[:options] || OptionHash.new(@default_opts).opts
  { options: o }
end

#req_sessionObject



35
36
37
# File 'lib/simple_session/base.rb', line 35

def req_session
  request.cookies[@key] if request
end

#session_changed?Boolean

Returns:

  • (Boolean)


107
108
109
# File 'lib/simple_session/base.rb', line 107

def session_changed?
  request.session != session
end


95
96
97
# File 'lib/simple_session/base.rb', line 95

def set_cookie_header headers, key, cookie
  Rack::Utils.set_cookie_header! headers, key, cookie
end

#signatureObject



115
116
117
# File 'lib/simple_session/base.rb', line 115

def signature
  hmac(cipher_key)
end

#unload_cipher(data) ⇒ Object



173
174
175
176
177
178
179
# File 'lib/simple_session/base.rb', line 173

def unload_cipher data
  cipher = new_cipher
  cipher.decrypt
  cipher.iv  = data[0, 16]
  cipher.key = cipher_key
  cipher.update(data[16..-1]) + cipher.final
end

#update_optionsObject



99
100
101
# File 'lib/simple_session/base.rb', line 99

def update_options
  session[:options] = {options: OptionHash.new(request.session_options).opts}
end

#update_sessionObject



111
112
113
# File 'lib/simple_session/base.rb', line 111

def update_session
  @session = request.session
end