Skip to content

Commit 9f6dafc

Browse files
committed
Inherit ActionDispatch::Session::AbstractSecureStore instead of Rack::Session::Abstract::Persisted
1 parent df4b777 commit 9f6dafc

16 files changed

+134
-537
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ group :docs do
1010
end
1111

1212
group :test do
13+
gem 'debug'
1314
gem 'rspec'
1415
gem 'simplecov', require: false
1516
gem 'rack-test'

README.md

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -84,34 +84,6 @@ garbage collection similar to below:
8484
The above example will clear sessions older than one day or that have been
8585
stale for longer than an hour.
8686

87-
### Locking Strategy
88-
89-
You may want the Session Store to implement the provided pessimistic locking
90-
strategy if you are concerned about concurrency issues with session accesses.
91-
By default, locking is not implemented for the session store. You must trigger
92-
the locking strategy through the configuration of the session store. Pessimistic
93-
locking, in this case, means that only one read can be made on a session at
94-
once. While the session is being read by the process with the lock, other
95-
processes may try to obtain a lock on the same session but will be blocked.
96-
97-
Locking is expensive and will drive up costs depending on how it is used.
98-
Without locking, one read and one write are performed per request for session
99-
data manipulation. If a locking strategy is implemented, as many as the total
100-
maximum wait time divided by the lock retry delay writes to the database.
101-
Keep these considerations in mind if you plan to enable locking.
102-
103-
#### Configuration for Locking
104-
105-
The following configuration options will allow you to configure the pessimistic
106-
locking strategy according to your needs:
107-
108-
options = {
109-
:enable_locking => true,
110-
:lock_expiry_time => 500,
111-
:lock_retry_delay => 500,
112-
:lock_max_wait_time => 1
113-
}
114-
11587
### Error Handling
11688

11789
You can pass in your own error handler for raised exceptions or you can allow

aws-sessionstore-dynamodb.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Gem::Specification.new do |spec|
1616
spec.require_paths = ["lib"]
1717

1818
spec.add_dependency 'aws-sdk-dynamodb', '~> 1'
19+
spec.add_dependency('actionpack', '>= 6.1')
1920
spec.add_dependency 'rack', '~> 3'
2021
spec.add_dependency 'rack-session', '>= 2.0.0'
2122
end

lib/aws-sessionstore-dynamodb.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ 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'
109
require 'aws/session_store/dynamo_db/lock_wait_timeout_error'
1110
require 'aws/session_store/dynamo_db/errors/base_handler'
1211
require 'aws/session_store/dynamo_db/errors/default_handler'
1312
require 'aws/session_store/dynamo_db/garbage_collection'
1413
require 'aws/session_store/dynamo_db/locking/base'
1514
require 'aws/session_store/dynamo_db/locking/null'
16-
require 'aws/session_store/dynamo_db/locking/pessimistic'
1715
require 'aws/session_store/dynamo_db/rack_middleware'
1816
require 'aws/session_store/dynamo_db/table'
1917
require 'aws/session_store/dynamo_db/version'

lib/aws/session_store/dynamo_db/configuration.rb

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ module Aws::SessionStore::DynamoDB
2323
# :error_handler as a cofniguration object. You must implement the BaseErrorHandler class.
2424
# @see BaseHandler Interface for Error Handling for DynamoDB Session Store.
2525
#
26-
# == Locking Strategy
27-
# By default, locking is not implemented for the session store. You must trigger the
28-
# locking strategy through the configuration of the session store. Pessimistic locking,
29-
# in this case, means that only one read can be made on a session at once. While the session
30-
# is being read by the process with the lock, other processes may try to obtain a lock on
31-
# the same session but will be blocked. See the accessors with lock in their name for
32-
# how to configure the pessimistic locking strategy to your needs.
33-
#
3426
# == DynamoDB Specific Options
3527
# You may configure the table name and table hash key value of your session table with
3628
# the :table_name and :table_key options. You may also configure performance options for
@@ -52,8 +44,7 @@ class Configuration
5244
:enable_locking => false,
5345
:lock_expiry_time => 500,
5446
:lock_retry_delay => 500,
55-
:lock_max_wait_time => 1,
56-
:secret_key => nil
47+
:lock_max_wait_time => 1
5748
}
5849

5950
### Feature options
@@ -94,26 +85,6 @@ class Configuration
9485
# before the current time that the session was last accessed.
9586
attr_reader :max_stale
9687

97-
# @return [true] Pessimistic locking strategy will be implemented for
98-
# all session accesses.
99-
# @return [false] No locking strategy will be implemented for
100-
# all session accesses.
101-
attr_reader :enable_locking
102-
103-
# @return [Integer] Time in milleseconds after which lock will expire.
104-
attr_reader :lock_expiry_time
105-
106-
# @return [Integer] Time in milleseconds to wait before retrying to obtain
107-
# lock once an attempt to obtain lock has been made and has failed.
108-
attr_reader :lock_retry_delay
109-
110-
# @return [Integer] Maximum time in seconds to wait to acquire lock
111-
# before giving up.
112-
attr_reader :lock_max_wait_time
113-
114-
# @return [String] The secret key for HMAC encryption.
115-
attr_reader :secret_key
116-
11788
# @return [String,Pathname]
11889
attr_reader :config_file
11990

@@ -159,20 +130,6 @@ class Configuration
159130
# from the current time that a session was created.
160131
# @option options [Integer] :max_stale (nil) Maximum number of seconds
161132
# before current time that session was last accessed.
162-
# @option options [String] :secret_key (nil) Secret key for HMAC encription.
163-
# @option options [Integer] :enable_locking (false) If true, a pessimistic
164-
# locking strategy will be implemented for all session accesses.
165-
# If false, no locking strategy will be implemented for all session
166-
# accesses.
167-
# @option options [Integer] :lock_expiry_time (500) Time in milliseconds
168-
# after which lock expires on session.
169-
# @option options [Integer] :lock_retry_delay (500) Time in milleseconds to
170-
# wait before retrying to obtain lock once an attempt to obtain lock
171-
# has been made and has failed.
172-
# @option options [Integer] :lock_max_wait_time (500) Maximum time
173-
# in seconds to wait to acquire lock before giving up.
174-
# @option options [String] :secret_key (SecureRandom.hex(64))
175-
# Secret key for HMAC encription.
176133
def initialize(options = {})
177134
@options = default_options.merge(
178135
env_options.merge(

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ class DefaultHandler < Aws::SessionStore::DynamoDB::Errors::BaseHandler
55
HARD_ERRORS = [
66
Aws::DynamoDB::Errors::ResourceNotFoundException,
77
Aws::DynamoDB::Errors::ConditionalCheckFailedException,
8-
Aws::SessionStore::DynamoDB::MissingSecretKeyError,
98
Aws::SessionStore::DynamoDB::LockWaitTimeoutError
109
]
1110

lib/aws/session_store/dynamo_db/locking/base.rb

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ def set_session_data(env, sid, session, options = {})
1313
return false if session.empty?
1414
packed_session = pack_data(session)
1515
handle_error(env) do
16-
save_opts = update_opts(env, sid, packed_session, options)
17-
@config.dynamo_db_client.update_item(save_opts)
16+
if env['dynamo_db.new_session']
17+
save_options = save_new_opts(env, sid, packed_session)
18+
@config.dynamo_db_client.put_item(save_options)
19+
env.delete('dynamo_db.new_session')
20+
else
21+
save_options = save_exists_opts(env, sid, packed_session, options)
22+
@config.dynamo_db_client.update_item(save_options)
23+
end
1824
sid
1925
end
2026
end
@@ -51,104 +57,82 @@ def handle_error(env = nil, &block)
5157

5258
# @return [Hash] Options for deleting session.
5359
def delete_opts(sid)
54-
table_opts(sid)
55-
end
56-
57-
# @return [Hash] Options for updating item in Session table.
58-
def update_opts(env, sid, session, options = {})
59-
if env['dynamo_db.new_session']
60-
updt_options = save_new_opts(env, sid, session)
61-
else
62-
updt_options = save_exists_opts(env, sid, session, options)
63-
end
64-
updt_options
60+
{
61+
table_name: @config.table_name,
62+
key: {
63+
@config.table_key => sid
64+
}
65+
}
6566
end
6667

6768
# @return [Hash] Options for saving a new session in database.
6869
def save_new_opts(env, sid, session)
69-
attribute_opts = attr_updts(env, session, created_attr)
70-
merge_all(table_opts(sid), attribute_opts)
70+
{
71+
table_name: @config.table_name,
72+
item: {
73+
@config.table_key => sid,
74+
data: session.to_s,
75+
created_at: created_at,
76+
updated_at: updated_at,
77+
expire_at: expire_at
78+
},
79+
condition_expression: "attribute_not_exists(#{@config.table_key})"
80+
}
7181
end
7282

7383
# @return [Hash] Options for saving an existing sesison in the database.
7484
def save_exists_opts(env, sid, session, options = {})
75-
add_attr = options[:add_attrs] || {}
76-
expected = options[:expect_attr] || {}
77-
attribute_opts = merge_all(attr_updts(env, session, add_attr), expected)
78-
merge_all(table_opts(sid), attribute_opts)
85+
data = if data_unchanged?(env, session)
86+
{}
87+
else
88+
{
89+
data: {
90+
value: session.to_s,
91+
action: 'PUT'
92+
}
93+
}
94+
end
95+
{
96+
table_name: @config.table_name,
97+
key: {
98+
@config.table_key => sid
99+
},
100+
attribute_updates: {
101+
updated_at: {
102+
value: updated_at,
103+
action: 'PUT'
104+
},
105+
expire_at: {
106+
value: expire_at,
107+
action: 'PUT'
108+
}
109+
}.merge(data),
110+
return_values: 'UPDATED_NEW'
111+
}
79112
end
80113

81114
# Unmarshal the data.
82115
def unpack_data(packed_data)
83116
Marshal.load(packed_data.unpack("m*").first)
84117
end
85118

86-
# Table options for client.
87-
def table_opts(sid)
88-
{
89-
:table_name => @config.table_name,
90-
:key => { @config.table_key => sid }
91-
}
92-
end
93-
94-
# Attributes to update via client.
95-
def attr_updts(env, session, add_attrs = {})
96-
data = data_unchanged?(env, session) ? {} : data_attr(session)
97-
{
98-
attribute_updates: merge_all(updated_attr, data, add_attrs, expire_attr),
99-
return_values: 'UPDATED_NEW'
100-
}
101-
end
102-
103-
# Update client with current time attribute.
104119
def updated_at
105-
{ :value => (Time.now).to_f, :action => "PUT" }
120+
Time.now.to_f
106121
end
107122

108-
# Attribute for creation of session.
109-
def created_attr
110-
{ "created_at" => updated_at }
123+
def created_at
124+
updated_at
111125
end
112126

113-
# Update client with current time + max_stale.
114127
def expire_at
115128
max_stale = @config.max_stale || 0
116-
{ value: (Time.now + max_stale).to_i, action: 'PUT' }
117-
end
118-
119-
# Attribute for TTL expiration of session.
120-
def expire_attr
121-
{ 'expire_at' => expire_at }
122-
end
123-
124-
# Attribute for updating session.
125-
def updated_attr
126-
{
127-
"updated_at" => updated_at
128-
}
129-
end
130-
131-
def data_attr(session)
132-
{ "data" => {:value => session, :action => "PUT"} }
129+
(Time.now + max_stale).to_i
133130
end
134131

135132
# Determine if data has been manipulated
136133
def data_unchanged?(env, session)
137134
return false unless env['rack.initial_data']
138135
env['rack.initial_data'] == session
139136
end
140-
141-
# Attributes to be retrieved via client
142-
def attr_opts
143-
{:attributes_to_get => ["data"],
144-
:consistent_read => @config.consistent_read}
145-
end
146-
147-
# @return [Hash] merged hash of all hashes passed in.
148-
def merge_all(*hashes)
149-
new_hash = {}
150-
hashes.each{|hash| new_hash.merge!(hash)}
151-
new_hash
152-
end
153137
end
154138
end

lib/aws/session_store/dynamo_db/locking/null.rb

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,24 @@ def get_session_data(env, sid)
1313

1414
# @return [Hash] Options for getting session.
1515
def get_session_opts(sid)
16-
merge_all(table_opts(sid), attr_opts)
16+
{
17+
table_name: @config.table_name,
18+
key: {
19+
@config.table_key => sid
20+
},
21+
attributes_to_get: [ "data" ],
22+
consistent_read: @config.consistent_read
23+
}
1724
end
1825

1926
# @return [String] Session data.
2027
def extract_data(env, result = nil)
21-
env['rack.initial_data'] = result[:item]["data"] if result[:item]
22-
unpack_data(result[:item]["data"]) if result[:item] && result[:item].has_key?("data")
28+
if result[:item] && result[:item].has_key?("data")
29+
env['rack.initial_data'] = result[:item]["data"]
30+
unpack_data(result[:item]["data"])
31+
else
32+
nil
33+
end
2334
end
2435

2536
end

0 commit comments

Comments
 (0)