file-rack 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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rack/session/file.rb +188 -0
  3. metadata +60 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9248733450c739c3a357513ae258df3041656687
4
+ data.tar.gz: e4a605b6a64f1e141015ab74a9e86e520682a6f5
5
+ SHA512:
6
+ metadata.gz: a7d3d5db06550f5b1236c4b1baf524e92667602582fe9aaf69bf4c80d9eb9ff5529315fb0e3a08c0c110cd3b39d561689c94fe6b819d9397aeae78b7edaa115b
7
+ data.tar.gz: '00296450b26275e88bcd05621721c1f8fd90d31956d1b24d56798d1fca3635dab6724e82cdbf74b38e2384e281f561b0d48f08d19d23a367952207ea0c2c904d'
@@ -0,0 +1,188 @@
1
+ require 'pstore'
2
+ require 'tmpdir'
3
+ require 'openssl'
4
+ require 'rack/request'
5
+ require 'rack/response'
6
+ require 'rack/session/abstract/id'
7
+
8
+ module Rack
9
+
10
+ module Session
11
+
12
+ # Rack::Session::File provides simple filed based session management.
13
+ # By default, the session is stored in /tmp while cookie holds only
14
+ # session id.
15
+ #
16
+ # When the :secret key is set (recommended), cookie data is checked
17
+ # for data integrity. The :old_secret key is also accepted allowing
18
+ # smooth secret rotation.
19
+ #
20
+ # Garbage collection is controlled via :gc_probability and :gc_maxlife.
21
+ # Every call to write_session, garbage collector is called with probability
22
+ # of :gc_probability. It scans :dir for sessions files and deletes ones
23
+ # with mtime older than :gc_maxlife.
24
+ #
25
+ # Supported options for constructor are:
26
+ #
27
+ # :dir directory into which save sessions
28
+ # :prefix session file prefix
29
+ # :key under what cookie save the session_id
30
+ # :domain domain should the session_id cookie is valid for
31
+ # :path path the session_id cookie is valid for
32
+ # :expire_after session_id cookie expires after this seconds
33
+ # :secret secret to use for integrity check
34
+ # :old_secret secret previously used, allowing smooth secret rotation
35
+ #
36
+ # :gc_probability probability of gc to run, in interval [0; 1]
37
+ # :gc_maxlife how old (in seconds) session files should be cleaned up
38
+ #
39
+ # Default values:
40
+ #
41
+ # :dir File.join(Dir.tmpdir(), 'file-rack')
42
+ # :prefix 'file-rack-session-'
43
+ # :key rack.session
44
+ # :domain nil
45
+ # :path nil
46
+ # :expire_after nil
47
+ # :secret nil
48
+ # :old_secret nil
49
+ # :gc_probability 0.01
50
+ # :gc_maxlife 1200
51
+ #
52
+ # Example:
53
+ #
54
+ # use Rack::Session::File, dir: '/tmp',
55
+ # prefix: 'session-',
56
+ #
57
+ # All parameters are optional.
58
+ class File < Abstract::Persisted
59
+
60
+ SESSION_ID = 'session_id'.freeze
61
+
62
+ def initialize(app, options = {})
63
+ @secrets = options.values_at(:secret, :old_secret).compact
64
+ @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
65
+
66
+ @dir = options[:dir] || ::File.join(Dir.tmpdir(), 'file-rack')
67
+ @prefix = options[:prefix] || 'file-rack-session-'
68
+ FileUtils.mkdir_p @dir
69
+
70
+ @gc_probability = options[:gc_probability] || 0.01
71
+ @gc_maxlife = options[:gc_maxlife] || 1200
72
+
73
+ warn <<~MSG unless secure?(options)
74
+ SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
75
+ This poses a security threat. It is strongly recommended that you
76
+ provide a secret to prevent exploits that may be possible from crafted
77
+ cookies. This will not be supported in future versions of Rack, and
78
+ future versions will even invalidate your existing user cookies.
79
+
80
+ Called from: #{caller[0]}.
81
+ MSG
82
+
83
+ super(app, options.merge!(cookie_only: false))
84
+ end
85
+
86
+ private
87
+
88
+ def find_session(req, sid)
89
+ data = load_data(req)
90
+ data = persistent_session_id(data)
91
+ [data[SESSION_ID], data]
92
+ end
93
+
94
+ def write_session(req, session_id, session, options)
95
+ if options[:renew]
96
+ session[SESSION_ID] = generate_sid
97
+ end
98
+
99
+ store = PStore.new(path_for_sid(session_id))
100
+ store.transaction { store[:session] = session }
101
+
102
+ try_gc_run!
103
+
104
+ if @secrets.first
105
+ "#{session_id}--#{generate_hmac(session_id, @secrets.first)}"
106
+ else
107
+ session_id
108
+ end
109
+ end
110
+
111
+ def delete_session(req, session_id, options)
112
+ begin
113
+ File.delete(path_for_sid(session_id))
114
+ rescue => e
115
+ warn "Cannot delete session #{session_id}: #{e}"
116
+ end
117
+
118
+ unless options[:drop]
119
+ generate_sid
120
+ else
121
+ nil
122
+ end
123
+ end
124
+
125
+ def load_data(req)
126
+ sid = req.cookies[@key]
127
+ if @secrets.size > 0 and sid
128
+ sid, digest = sid.split('--', 2)
129
+ sid = nil unless digest_match?(sid, digest)
130
+ end
131
+ if sid
132
+ store = PStore.new(path_for_sid(sid), read_only: true)
133
+ store.transaction { store[:session] }
134
+ else
135
+ {}
136
+ end
137
+ end
138
+ def persistent_session_id(data, sid = nil)
139
+ data ||= {}
140
+ data[SESSION_ID] ||= sid
141
+ unless data[SESSION_ID]
142
+ begin
143
+ data[SESSION_ID] = generate_sid
144
+ end while ::File.exist? path_for_sid(data[SESSION_ID])
145
+ end
146
+ data
147
+ end
148
+
149
+ def path_for_sid(sid)
150
+ ::File.join @dir, "#{@prefix}#{sid}"
151
+ end
152
+
153
+ def digest_match?(data, digest)
154
+ return unless data && digest
155
+ @secrets.any? do |secret|
156
+ Rack::Utils.secure_compare(digest, generate_hmac(data, secret))
157
+ end
158
+ end
159
+
160
+ def generate_hmac(data, secret)
161
+ OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
162
+ end
163
+
164
+ def secure?(options)
165
+ @secrets.size >= 1
166
+ end
167
+
168
+ def try_gc_run!
169
+ return unless Random.rand < @gc_probability
170
+
171
+ threshold = Time.now - @gc_maxlife
172
+ Dir.chdir(@dir) do
173
+ Dir.entries(@dir).each do |entry|
174
+ next unless entry[/#{@prefix}/]
175
+ begin
176
+ ::File.delete(entry) if ::File.mtime(entry) < threshold
177
+ rescue => e
178
+ warn "Cannot delete session file #{entry}: #{e}"
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ end
187
+
188
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: file-rack
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Gray Wolf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ description: 'File-based rack session mechanism keeping only session_id in the cookie.
28
+
29
+ '
30
+ email: file-rack@wolfsden.cz
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/rack/session/file.rb
36
+ homepage: https://rubygems.org/gems/file-rack
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.6.13
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: File-based rack session mechanism
60
+ test_files: []