Skip to content

Commit 336c2d5

Browse files
committed
Add support for legacy format session ids
1 parent b6eb684 commit 336c2d5

File tree

7 files changed

+64
-5
lines changed

7 files changed

+64
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Run the session store as a Rack middleware in the following way:
2323
use Aws::SessionStore::DynamoDB::RackMiddleware.new(options)
2424
run SomeRackApp
2525

26-
Note that `:secret_key` is a mandatory configuration option that must be set.
26+
Note that `:secret_key` is a configuration option that is used for the older version.
2727

2828
## Detailed Usage
2929

lib/aws-sessionstore-dynamodb.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module DynamoDB; end
66

77
require 'aws/session_store/dynamo_db/configuration'
88
require 'aws/session_store/dynamo_db/invalid_id_error'
9+
require 'aws/session_store/dynamo_db/missing_secret_key_error'
910
require 'aws/session_store/dynamo_db/errors/base_handler'
1011
require 'aws/session_store/dynamo_db/errors/default_handler'
1112
require 'aws/session_store/dynamo_db/garbage_collection'

lib/aws/session_store/dynamo_db/configuration.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class Configuration
4040
:write_capacity => 5,
4141
:raise_errors => false,
4242
# :max_age => 7*3600*24,
43-
# :max_stale => 3600*5
43+
# :max_stale => 3600*5,
44+
:secret_key => nil
4445
}
4546

4647
### Feature options
@@ -81,6 +82,9 @@ class Configuration
8182
# before the current time that the session was last accessed.
8283
attr_reader :max_stale
8384

85+
# @return [String] The secret key for HMAC encryption of legacy sid.
86+
attr_reader :secret_key
87+
8488
# @return [String,Pathname]
8589
attr_reader :config_file
8690

@@ -126,6 +130,8 @@ class Configuration
126130
# from the current time that a session was created.
127131
# @option options [Integer] :max_stale (nil) Maximum number of seconds
128132
# before current time that session was last accessed.
133+
# @option options [String] :secret_key (SecureRandom.hex(64))
134+
# Secret key for HMAC encription.
129135
def initialize(options = {})
130136
@options = default_options.merge(
131137
env_options.merge(

lib/aws/session_store/dynamo_db/errors/default_handler.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ class DefaultHandler < Aws::SessionStore::DynamoDB::Errors::BaseHandler
44
# Array of errors that will always be passed up the Rack stack.
55
HARD_ERRORS = [
66
Aws::DynamoDB::Errors::ResourceNotFoundException,
7-
Aws::DynamoDB::Errors::ConditionalCheckFailedException
7+
Aws::DynamoDB::Errors::ConditionalCheckFailedException,
8+
Aws::SessionStore::DynamoDB::MissingSecretKeyError
89
]
910

1011
# Determines behavior of DefaultErrorHandler
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module Aws::SessionStore::DynamoDB
2+
class MissingSecretKeyError < RuntimeError
3+
def initialize(msg = "No secret key provided!")
4+
super
5+
end
6+
end
7+
end

lib/aws/session_store/dynamo_db/rack_middleware.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,32 @@ def find_session(req, sid)
2323
if req.session.options[:skip]
2424
[generate_sid, {}]
2525
else
26-
unless sid and session = @lock.get_session_data(req.env, sid.private_id)
26+
session = find_session_data(req, sid)
27+
unless session
2728
session = {}
2829
sid = generate_unique_sid(req.env, session)
2930
end
3031
[sid, session]
3132
end
3233
end
3334

35+
# Generate HMAC hash based on MD5
36+
def generate_hmac(sid, secret)
37+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::MD5.new, secret, sid).strip()
38+
end
39+
40+
# Get session data from DynamoDB.
41+
def find_session_data(req, sid)
42+
return nil unless sid
43+
digest, ver_sid = sid.public_id.split("--")
44+
if ver_sid && @config.secret_key && digest == generate_hmac(ver_sid, @config.secret_key)
45+
# Legacy session id format
46+
@lock.get_session_data(req.env, sid.public_id)
47+
else
48+
@lock.get_session_data(req.env, sid.private_id)
49+
end
50+
end
51+
3452
def generate_unique_sid(env, session)
3553
env['dynamo_db.new_session'] = 'true'
3654
generate_sid

spec/aws/session_store/dynamo_db/rack_middleware_spec.rb

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ def ensure_data_updated(mutated_data)
3535

3636
before do
3737
@options = {
38-
dynamo_db_client: dynamo_db_client
38+
dynamo_db_client: dynamo_db_client,
39+
secret_key: secret_key
3940
}
4041
end
4142

43+
let(:secret_key) { 'watermelon_cherries' }
4244
let(:app) { RoutedRackApp.build(@options) }
4345
let(:sample_packed_data) do
4446
[Marshal.dump('multiplier' => 1)].pack('m*')
@@ -140,6 +142,30 @@ def ensure_data_updated(mutated_data)
140142
get '/', { 'HTTP_Cookie' => session_cookie }
141143
end
142144
end
145+
146+
describe 'Legacy session id format' do
147+
let(:legacy_session_id) do
148+
sid = SecureRandom.hex(16)
149+
sid.encode!(Encoding::UTF_8)
150+
"#{OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('MD5'), secret_key, sid).strip}--" + sid
151+
end
152+
153+
it 'loads/manipulates a session based on legacy session id' do
154+
expect(dynamo_db_client).to receive(:get_item).with(
155+
{
156+
:attributes_to_get => ["data"],
157+
:consistent_read => true,
158+
:key => {
159+
"session_id" => legacy_session_id
160+
},
161+
:table_name=>"sessions"
162+
}
163+
)
164+
set_cookie("_session_id=#{legacy_session_id}; path=/; httponly")
165+
get '/'
166+
expect(last_request.session.to_hash).to eq('multiplier' => 2)
167+
end
168+
end
143169
end
144170
end
145171
end

0 commit comments

Comments
 (0)