rack-session-xfile 0.10.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.rdoc +11 -5
- data/lib/rack/session/xfile.rb +25 -32
- data/spec/spec_xfile.rb +30 -27
- data/xfile.gemspec +3 -3
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 155a1b34b938b76714dc113c9563c6682c5aa6b1
|
4
|
+
data.tar.gz: 12fe040425c0b65a29159fb4ea810e95fc8123e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08a5a07e0fb3405ee3edaee1f12bf7a506fb341abea2f80db682f08104c452590701214d5a48847e6fbaa7952f5f8c7bd92825dd82b85ddb22e5c189c90947af'
|
7
|
+
data.tar.gz: 2a83b4923d9c3aaab1203870280ed08b10a24d5c2b95a4efcaa8bbcce69a06c30cec57c776bf6988cd16f965e19ab020af62bf8d519c83ac6aa56916cd196909
|
data/README.rdoc
CHANGED
@@ -128,28 +128,34 @@ or open an issue on the bug tracker.
|
|
128
128
|
|
129
129
|
== Links
|
130
130
|
|
131
|
-
Homepage ::
|
131
|
+
Homepage :: https://ecentryx.com/gems/rack-session-xfile
|
132
132
|
Ruby Gem :: https://rubygems.org/gems/rack-session-xfile
|
133
133
|
Source Code :: https://bitbucket.org/pachl/rack-session-xfile/src
|
134
134
|
Bug Tracker :: https://bitbucket.org/pachl/rack-session-xfile/issues
|
135
135
|
|
136
136
|
== Compatibility
|
137
137
|
|
138
|
-
Rack::Session::XFile was developed and tested on
|
139
|
-
OpenBSD[
|
138
|
+
Rack::Session::XFile was originally developed and tested on
|
139
|
+
OpenBSD[https://www.openbsd.org] 5.6
|
140
140
|
using Ruby[https://www.ruby-lang.org] 2.1.2
|
141
141
|
and Rack[https://github.com/rack/rack] 1.6.0.
|
142
142
|
|
143
|
+
Currently, Rack::Session::XFile is compatible with Rack 2.0 and Ruby 2.2+ only.
|
144
|
+
|
143
145
|
== History
|
144
146
|
|
145
147
|
1. 2015-01-29, v0.10.0: First public release
|
146
148
|
* XFile ran in production for more than a year before public release.
|
147
149
|
|
150
|
+
2. 2017-06-07, v1.0.0
|
151
|
+
* Update for compatibility with Rack 2.0 and Ruby 2.2+
|
152
|
+
(breaks compatibility with Rack 1.x; use XFile 0.10.x)
|
153
|
+
|
148
154
|
== License
|
149
155
|
|
150
|
-
({ISC License}[
|
156
|
+
({ISC License}[https://opensource.org/licenses/ISC])
|
151
157
|
|
152
|
-
Copyright (c)
|
158
|
+
Copyright (c) 2014-2017, Clint Pachl <pachl@ecentryx.com>
|
153
159
|
|
154
160
|
Permission to use, copy, modify, and/or distribute this software for any purpose
|
155
161
|
with or without fee is hereby granted, provided that the above copyright notice
|
data/lib/rack/session/xfile.rb
CHANGED
@@ -4,15 +4,15 @@ require 'tmpdir'
|
|
4
4
|
module Rack
|
5
5
|
module Session
|
6
6
|
|
7
|
-
class XFile < Abstract::
|
7
|
+
class XFile < Abstract::Persisted
|
8
8
|
|
9
|
-
VERSION = '0.
|
9
|
+
VERSION = '1.0.0'
|
10
10
|
|
11
11
|
File = ::File # override Rack::File
|
12
12
|
|
13
13
|
SIDCharacters = [*(0..9), *(:a..:z), *(:A..:Z)].map!(&:to_s)
|
14
14
|
|
15
|
-
Abstract::
|
15
|
+
DEFAULT_OPTIONS = Abstract::Persisted::DEFAULT_OPTIONS.merge \
|
16
16
|
session_dir: File.join(Dir.tmpdir, 'xfile-sessions'),
|
17
17
|
user_agent_filter: nil,
|
18
18
|
key: 'xid'
|
@@ -20,32 +20,32 @@ module Rack
|
|
20
20
|
def initialize(app, options = {})
|
21
21
|
super
|
22
22
|
@mutex = Mutex.new
|
23
|
-
@session_dir =
|
24
|
-
@ua_filter =
|
25
|
-
@sid_nbytes =
|
23
|
+
@session_dir = default_options[:session_dir]
|
24
|
+
@ua_filter = default_options[:user_agent_filter]
|
25
|
+
@sid_nbytes = default_options[:sidbits] / 8
|
26
26
|
setup_directories
|
27
27
|
check_permissions
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
critical_section(
|
32
|
-
|
30
|
+
def write_session(req, sid, session, options)
|
31
|
+
critical_section(req) do
|
32
|
+
write_session_file(sid, session)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
38
|
-
return [nil, {}] if filter_request(env, sid)
|
36
|
+
def find_session(req, sid)
|
37
|
+
return [nil, {}] if filter_request(req, sid)
|
39
38
|
|
40
|
-
|
39
|
+
critical_section(req) do
|
40
|
+
unless sid and session = read_session_file(sid)
|
41
41
|
sid, session = generate_sid, {}
|
42
42
|
end
|
43
43
|
[sid, session]
|
44
44
|
end
|
45
45
|
end
|
46
|
-
|
47
|
-
def
|
48
|
-
critical_section(
|
46
|
+
|
47
|
+
def delete_session(req, sid, options)
|
48
|
+
critical_section(req) do
|
49
49
|
File.unlink(session_file(sid)) rescue nil
|
50
50
|
generate_sid unless options[:drop]
|
51
51
|
end
|
@@ -63,8 +63,8 @@ module Rack
|
|
63
63
|
# Some systems do not implement, or implement correctly, advisory file
|
64
64
|
# locking for threads; therefore a mutex lock must encompass file locks.
|
65
65
|
#
|
66
|
-
def critical_section(
|
67
|
-
@mutex.lock if
|
66
|
+
def critical_section(req)
|
67
|
+
@mutex.lock if req.multithread?
|
68
68
|
yield
|
69
69
|
ensure
|
70
70
|
@mutex.unlock if @mutex.locked?
|
@@ -74,7 +74,6 @@ module Rack
|
|
74
74
|
# Atomically generate a unique session ID and allocate the resource file.
|
75
75
|
# Returns the session ID.
|
76
76
|
# --
|
77
|
-
# NOTE
|
78
77
|
# The sid is also a filename. Only SIDCharacters are allowed in the sid.
|
79
78
|
# If +super+ is called it returns a HEX sid, which is a subset.
|
80
79
|
#
|
@@ -90,7 +89,7 @@ module Rack
|
|
90
89
|
#
|
91
90
|
# Read session from file using a shared lock.
|
92
91
|
#
|
93
|
-
def
|
92
|
+
def read_session_file(sid)
|
94
93
|
File.open(session_file(sid)) do |f|
|
95
94
|
f.flock(File::LOCK_SH)
|
96
95
|
begin
|
@@ -106,7 +105,7 @@ module Rack
|
|
106
105
|
#
|
107
106
|
# Write session to file using an exclusive lock.
|
108
107
|
#
|
109
|
-
def
|
108
|
+
def write_session_file(sid, session)
|
110
109
|
File.open(session_file(sid), 'r+') do |f|
|
111
110
|
f.flock(File::LOCK_EX)
|
112
111
|
f.write(Marshal.dump(session))
|
@@ -115,7 +114,7 @@ module Rack
|
|
115
114
|
end
|
116
115
|
sid
|
117
116
|
rescue => e
|
118
|
-
warn "#{self.class} cannot
|
117
|
+
warn "#{self.class} cannot write session: #{e.message}"
|
119
118
|
false
|
120
119
|
end
|
121
120
|
|
@@ -135,20 +134,14 @@ module Rack
|
|
135
134
|
#
|
136
135
|
# sid bit range: 62^10-62^50 (approx. 2^60-2^297)
|
137
136
|
#
|
138
|
-
def filter_request(
|
139
|
-
|
140
|
-
|
141
|
-
if @ua_filter
|
142
|
-
request = Request.new(env)
|
143
|
-
if request.user_agent =~ @ua_filter
|
144
|
-
return request.session.options[:skip] = true
|
145
|
-
end
|
137
|
+
def filter_request(req, sid)
|
138
|
+
if @ua_filter and req.user_agent =~ @ua_filter
|
139
|
+
return req.session.options[:skip] = true
|
146
140
|
end
|
147
141
|
|
148
142
|
if sid and sid !~ /^[a-zA-Z0-9]{10,50}$/
|
149
143
|
warn "#{self.class} SID=#{sid}: tampered sid detected."
|
150
|
-
|
151
|
-
request.session.options[:skip] = true
|
144
|
+
req.session.options[:skip] = true
|
152
145
|
end
|
153
146
|
end
|
154
147
|
|
data/spec/spec_xfile.rb
CHANGED
@@ -10,10 +10,6 @@ describe Rack::Session::XFile do
|
|
10
10
|
%[{"counter"=>#{number}}]
|
11
11
|
end
|
12
12
|
|
13
|
-
def empty_session
|
14
|
-
'{}'
|
15
|
-
end
|
16
|
-
|
17
13
|
def set_sid id
|
18
14
|
if id =~ TheSession
|
19
15
|
{'HTTP_COOKIE' => id}
|
@@ -26,7 +22,7 @@ describe Rack::Session::XFile do
|
|
26
22
|
def create_session data = nil
|
27
23
|
app = Rack::Session::XFile.new(nil)
|
28
24
|
sid = app.send(:generate_sid)
|
29
|
-
app.
|
25
|
+
app.write_session(EMPTY_REQUEST, sid, (data || {sid: sid}), {})
|
30
26
|
sid
|
31
27
|
end
|
32
28
|
|
@@ -37,13 +33,18 @@ describe Rack::Session::XFile do
|
|
37
33
|
session_bits = Rack::Session::XFile::DEFAULT_OPTIONS[:sidbits]
|
38
34
|
session_chars = (session_bits / 8 / 3.0 * 4).ceil # urlsafe_base64 length
|
39
35
|
TheSession = /^#{@session_key}=[0-9a-zA-Z]{#{session_chars}};/
|
36
|
+
SESSION = 'rack.session'.freeze
|
37
|
+
SESSION_OPTS = 'rack.session.options'.freeze
|
38
|
+
EMPTY_SESSION = '{}'.freeze
|
39
|
+
EMPTY_REQUEST = Rack::Request.new({}).freeze
|
40
|
+
|
40
41
|
|
41
42
|
# Apps
|
42
43
|
|
43
44
|
incrementor = lambda do |env|
|
44
|
-
env[
|
45
|
-
env[
|
46
|
-
Rack::Response.new(env[
|
45
|
+
env[SESSION]['counter'] ||= 0
|
46
|
+
env[SESSION]['counter'] += 1
|
47
|
+
Rack::Response.new(env[SESSION].inspect).to_a
|
47
48
|
end
|
48
49
|
|
49
50
|
noop = lambda do |env|
|
@@ -51,26 +52,26 @@ describe Rack::Session::XFile do
|
|
51
52
|
end
|
52
53
|
|
53
54
|
read_session_id = lambda do |env|
|
54
|
-
Rack::Response.new(env[
|
55
|
+
Rack::Response.new(env[SESSION].id).to_a
|
55
56
|
end
|
56
57
|
|
57
58
|
read_session = lambda do |env|
|
58
|
-
env[
|
59
|
-
Rack::Response.new(env[
|
59
|
+
env[SESSION].send(:load_for_read!)
|
60
|
+
Rack::Response.new(env[SESSION].inspect).to_a
|
60
61
|
end
|
61
62
|
|
62
63
|
defer_session = lambda do |env|
|
63
|
-
env[
|
64
|
+
env[SESSION_OPTS][:defer] = true
|
64
65
|
incrementor.call(env)
|
65
66
|
end
|
66
67
|
|
67
68
|
drop_session = lambda do |env|
|
68
|
-
env[
|
69
|
+
env[SESSION_OPTS][:drop] = true
|
69
70
|
incrementor.call(env)
|
70
71
|
end
|
71
72
|
|
72
73
|
renew_session = lambda do |env|
|
73
|
-
env[
|
74
|
+
env[SESSION_OPTS][:renew] = true
|
74
75
|
incrementor.call(env)
|
75
76
|
end
|
76
77
|
|
@@ -149,24 +150,25 @@ describe Rack::Session::XFile do
|
|
149
150
|
@warnings.first.should.include 'group owned/accessible'
|
150
151
|
end
|
151
152
|
|
152
|
-
it 'returns false when
|
153
|
+
it 'returns false when writing session if cannot serialize data' do
|
153
154
|
obj = Object.new
|
154
155
|
def obj.singleton() nil end
|
155
|
-
|
156
|
+
nonserializable = { data: obj }
|
156
157
|
|
157
158
|
sid = create_session
|
158
159
|
app = Rack::Session::XFile.new(noop)
|
159
|
-
app.
|
160
|
+
app.write_session(EMPTY_REQUEST, sid, nonserializable, {}).
|
161
|
+
should.be.false
|
160
162
|
@warnings.count.should.equal 1
|
161
|
-
@warnings.first.should.
|
163
|
+
@warnings.first.should.include 'cannot write session: singleton'
|
162
164
|
end
|
163
165
|
|
164
|
-
it 'returns false when
|
166
|
+
it 'returns false when writing session if sid is nonexistent' do
|
165
167
|
sid = 'nonexistent'
|
166
168
|
app = Rack::Session::XFile.new(noop)
|
167
|
-
app.
|
169
|
+
app.write_session(EMPTY_REQUEST, sid, {}, {}).should.be.false
|
168
170
|
@warnings.count.should.equal 1
|
169
|
-
@warnings.first.should.
|
171
|
+
@warnings.first.should.include 'cannot write session: No such file'
|
170
172
|
end
|
171
173
|
|
172
174
|
it 'creates new xfile session' do
|
@@ -206,7 +208,7 @@ describe Rack::Session::XFile do
|
|
206
208
|
|
207
209
|
it 'merges session data from middleware in front' do
|
208
210
|
app = Rack::Session::XFile.new(incrementor)
|
209
|
-
res = Rack::MockRequest.new(app).get('/',
|
211
|
+
res = Rack::MockRequest.new(app).get('/', SESSION => {foo: 'bar'})
|
210
212
|
res.body.should.match /counter/
|
211
213
|
res.body.should.match /foo/
|
212
214
|
end
|
@@ -214,8 +216,9 @@ describe Rack::Session::XFile do
|
|
214
216
|
it 'creates new session when xfile is nonexistent' do
|
215
217
|
app = Rack::Session::XFile.new(read_session)
|
216
218
|
res = Rack::MockRequest.new(app).get('/', set_sid('nonexistent'))
|
217
|
-
res.body.should.equal
|
219
|
+
res.body.should.equal EMPTY_SESSION
|
218
220
|
res['Set-Cookie'].should.match TheSession
|
221
|
+
app.session_count.should.equal 1
|
219
222
|
end
|
220
223
|
|
221
224
|
it 'creates new session when xfile is empty' do
|
@@ -226,7 +229,7 @@ describe Rack::Session::XFile do
|
|
226
229
|
File.size(xfile).should.be.zero
|
227
230
|
|
228
231
|
res = Rack::MockRequest.new(app).get('/', set_sid(sid))
|
229
|
-
res.body.should.equal
|
232
|
+
res.body.should.equal EMPTY_SESSION
|
230
233
|
res['Set-Cookie'].should.be.nil # because sid already exists
|
231
234
|
app.session_count.should.equal 1
|
232
235
|
File.size(xfile).should.not.be.zero
|
@@ -244,7 +247,7 @@ describe Rack::Session::XFile do
|
|
244
247
|
corrupt_data = Marshal.dump(session_data).insert(i, 'FUBAR')
|
245
248
|
File.write(session_file, corrupt_data)
|
246
249
|
res = Rack::MockRequest.new(app).get('/', set_sid(sid))
|
247
|
-
res.body.should.equal
|
250
|
+
res.body.should.equal EMPTY_SESSION
|
248
251
|
res['Set-Cookie'].should.be.nil
|
249
252
|
end
|
250
253
|
|
@@ -258,12 +261,12 @@ describe Rack::Session::XFile do
|
|
258
261
|
app = Rack::Session::XFile.new(read_session)
|
259
262
|
|
260
263
|
res1 = Rack::MockRequest.new(app).get('/', set_sid('%2e%2e%2f%2e%2e%2f%00'))
|
261
|
-
res1.body.should.equal
|
264
|
+
res1.body.should.equal EMPTY_SESSION
|
262
265
|
res1['Set-Cookie'].should.be.nil
|
263
266
|
app.session_count.should.be.zero
|
264
267
|
|
265
268
|
res2 = Rack::MockRequest.new(app).get('/', set_sid('../../../../etc/passwd'))
|
266
|
-
res2.body.should.equal
|
269
|
+
res2.body.should.equal EMPTY_SESSION
|
267
270
|
res2['Set-Cookie'].should.be.nil
|
268
271
|
app.session_count.should.be.zero
|
269
272
|
|
data/xfile.gemspec
CHANGED
@@ -3,7 +3,7 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.version = File.read('lib/rack/session/xfile.rb')[/VERSION = '(.*)'/, 1]
|
4
4
|
s.author = 'Clint Pachl'
|
5
5
|
s.email = 'pachl@ecentryx.com'
|
6
|
-
s.homepage = '
|
6
|
+
s.homepage = 'https://ecentryx.com/gems/rack-session-xfile'
|
7
7
|
s.license = 'ISC'
|
8
8
|
s.summary = 'File-based session management with eXclusive R/W locking.'
|
9
9
|
s.description = <<-EOS
|
@@ -13,7 +13,7 @@ suitable for multi-threaded and multi-process applications.
|
|
13
13
|
EOS
|
14
14
|
s.files = Dir['Rakefile', 'README*', '*.gemspec', 'lib/**/*.rb', 'spec/*']
|
15
15
|
s.test_files = Dir['spec/spec_*.rb']
|
16
|
-
s.required_ruby_version = '>=
|
17
|
-
s.add_runtime_dependency 'rack', '~>
|
16
|
+
s.required_ruby_version = '>= 2.2'
|
17
|
+
s.add_runtime_dependency 'rack', '~> 2.0'
|
18
18
|
s.add_development_dependency 'bacon', '~> 1.2'
|
19
19
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-session-xfile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clint Pachl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bacon
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +53,7 @@ files:
|
|
53
53
|
- lib/rack/session/xfile.rb
|
54
54
|
- spec/spec_xfile.rb
|
55
55
|
- xfile.gemspec
|
56
|
-
homepage:
|
56
|
+
homepage: https://ecentryx.com/gems/rack-session-xfile
|
57
57
|
licenses:
|
58
58
|
- ISC
|
59
59
|
metadata: {}
|
@@ -65,7 +65,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: '2.2'
|
69
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
71
|
- - ">="
|
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
73
|
version: '0'
|
74
74
|
requirements: []
|
75
75
|
rubyforge_project:
|
76
|
-
rubygems_version: 2.
|
76
|
+
rubygems_version: 2.6.11
|
77
77
|
signing_key:
|
78
78
|
specification_version: 4
|
79
79
|
summary: File-based session management with eXclusive R/W locking.
|