sequel-sessions 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4201628cc2942c6e826292a9821a3e7e74d25eb6
4
+ data.tar.gz: 439063bfe5bbfa96d88d2285b2fc6e637925101e
5
+ SHA512:
6
+ metadata.gz: 073f31a44929aa4a89784c2bd37fe261013be2ac0d456a0a656db1c83ce38bc1ed56339409bfc63feec9373dfb1410f9dda8dc866877c43b22e94b97f9b9d070
7
+ data.tar.gz: 759b68e936ba516e84adabfb3038dfbec45fc8e7aa9c96ed89c320aa2a784411ba7a556d31c159ddf9aa25556704b47dd26fc125f667d158f48f2069b30ce250
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2017 Tom Wardrop
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ [sequel-sessions]
2
+ =================
3
+
4
+ Sequel-based session middleware for Rack 2.0 and above.
5
+
6
+ https://github.com/wardrop/sequel-sessions
7
+
8
+ ## Installation
9
+
10
+ gem install sequel-sessions
11
+
12
+ ## Usage
13
+
14
+ Simplest example using in-memory SQLite database:
15
+
16
+ ```ruby
17
+ use Rack::Session::Sequel, Sequel.sqlite
18
+ ```
19
+
20
+ A more fleshed out example with multiple options:
21
+
22
+ ```ruby
23
+ db_connection = Sequel.connect(adapter: 'mysql', host: 'localhost', username: 'root', database: 'blog')
24
+ use Rack::Session::Sequel, db: db_connection, table_name: :user_sessions, :expire_after => 3600
25
+ ```
26
+
27
+ ## License
28
+ Copyright © 2017 Tom Wardrop. Distributed under the [MIT license](http://www.opensource.org/licenses/mit-license).
@@ -0,0 +1,74 @@
1
+ module Rack
2
+ module Session
3
+ class Sequel < Abstract::Persisted
4
+
5
+ DEFAULT_OPTIONS = Abstract::Persisted::DEFAULT_OPTIONS.merge(:table_name => :sessions)
6
+
7
+ # Options arguments can be either a Sequel::Database instance, or a hash. Options beyond those defined in
8
+ # Rack::Session::Abstract::Persisted include:
9
+ # :db => The a Sequel database object.
10
+ # :table_name => The name of the table to store session data. Defaults to :sessions.
11
+ def initialize(app, options={})
12
+ options = {:db => options } if options.is_a? ::Sequel::Database
13
+ super
14
+ @mutex = Mutex.new
15
+ setup_database
16
+ end
17
+
18
+ def find_session(env, sid)
19
+ record = table.filter(sid: sid).first
20
+ if record
21
+ session = Marshal.load(record[:session].unpack('m*').first)
22
+ else
23
+ sid, session = generate_sid, {}
24
+ end
25
+ [sid, session]
26
+ end
27
+
28
+ def write_session(req, sid, session, options)
29
+ with_lock(req) do
30
+ begin
31
+ table.insert(sid: sid, session: [Marshal.dump(session)].pack('m*'), :created_at => Time.now.utc)
32
+ rescue ::Sequel::UniqueConstraintViolation
33
+ table.filter(sid: sid).update(sid: sid, session: [Marshal.dump(session)].pack('m*'), :updated_at => Time.now.utc)
34
+ end
35
+ sid
36
+ end
37
+ end
38
+
39
+ def delete_session(req, sid, options)
40
+ with_lock(req) do
41
+ table.filter(sid: sid).delete
42
+ generate_sid unless options[:drop]
43
+ end
44
+ end
45
+
46
+ def db
47
+ @default_options[:db]
48
+ end
49
+
50
+ def table
51
+ db[@default_options[:table_name].to_sym]
52
+ end
53
+
54
+ protected
55
+
56
+ def with_lock(req)
57
+ @mutex.lock if req.env['rack.multithread']
58
+ yield
59
+ ensure
60
+ @mutex.unlock if @mutex.locked?
61
+ end
62
+
63
+ def setup_database
64
+ db.create_table(@default_options[:table_name]) do
65
+ String :sid, :unique => true, :null => false, :primary_key => true
66
+ text :session, :null => false
67
+ DateTime :created_at, :null => false
68
+ DateTime :updated_at
69
+ end unless db.table_exists? @default_options[:table_name]
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,7 @@
1
+ module Rack
2
+ module Session
3
+ class Sequel
4
+ VERSION = "1.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ require 'rack'
2
+ require 'rack/session/abstract/id'
3
+ require 'sequel'
4
+ require 'rack/session/sequel'
5
+ require 'rack/session/version'
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'rack/session/version'
3
+
4
+ Gem::Specification.new 'sequel-sessions', Rack::Session::Sequel::VERSION do |s|
5
+ s.summary = 'Sequel-based session middleware for Rack 2.0+'
6
+ s.description = 'Sequel-based session middleware for Rack 2.0+'
7
+ s.authors = ['Tom Wardrop']
8
+ s.email = 'tom@tomwardrop.com'
9
+ s.homepage = 'https://github.com/wardrop/sequel-sessions'
10
+ s.license = 'MIT'
11
+ s.files = Dir.glob(`git ls-files`.split("\n") - %w[.gitignore])
12
+ s.test_files = Dir.glob('spec/**/*_spec.rb')
13
+
14
+ s.required_ruby_version = '>= 2.0.0'
15
+
16
+ s.add_runtime_dependency "rack", '>= 2.0'
17
+ s.add_runtime_dependency "sequel", '>= 4.0'
18
+ s.add_development_dependency "sqlite3", '>= 1.3'
19
+ s.add_development_dependency "minitest", '>= 5.1'
20
+ end
@@ -0,0 +1,217 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ # Based on Rack's test/spec_session_pool.rb.
4
+ require 'minitest/autorun'
5
+ require 'thread'
6
+ require 'rack/lint'
7
+ require 'rack/mock'
8
+ require 'sequel-sessions'
9
+
10
+ describe Rack::Session::Sequel do
11
+
12
+ session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key]
13
+ session_match = /#{session_key}=[0-9a-fA-F]+;/
14
+
15
+ let(:db) { Sequel.sqlite }
16
+
17
+ incrementor = lambda do |env|
18
+ env["rack.session"]["counter"] ||= 0
19
+ env["rack.session"]["counter"] += 1
20
+ Rack::Response.new(env["rack.session"].inspect).to_a
21
+ end
22
+
23
+ session_id = Rack::Lint.new(lambda do |env|
24
+ Rack::Response.new(env["rack.session"].inspect).to_a
25
+ end)
26
+
27
+ nothing = Rack::Lint.new(lambda do |env|
28
+ Rack::Response.new("Nothing").to_a
29
+ end)
30
+
31
+ drop_session = Rack::Lint.new(lambda do |env|
32
+ env['rack.session.options'][:drop] = true
33
+ incrementor.call(env)
34
+ end)
35
+
36
+ renew_session = Rack::Lint.new(lambda do |env|
37
+ env['rack.session.options'][:renew] = true
38
+ incrementor.call(env)
39
+ end)
40
+
41
+ defer_session = Rack::Lint.new(lambda do |env|
42
+ env['rack.session.options'][:defer] = true
43
+ incrementor.call(env)
44
+ end)
45
+
46
+ incrementor = Rack::Lint.new(incrementor)
47
+
48
+ it "creates a new cookie" do
49
+ pool = Rack::Session::Sequel.new(incrementor, db)
50
+ res = Rack::MockRequest.new(pool).get("/")
51
+ res["Set-Cookie"].must_include "#{session_key}="
52
+ res.body.must_equal '{"counter"=>1}'
53
+ end
54
+
55
+ it "determines session from a cookie" do
56
+ middleware = Rack::Session::Sequel.new(incrementor, db)
57
+ req = Rack::MockRequest.new(middleware)
58
+ cookie = req.get("/")["Set-Cookie"]
59
+ req.get("/", "HTTP_COOKIE" => cookie).
60
+ body.must_equal '{"counter"=>2}'
61
+ req.get("/", "HTTP_COOKIE" => cookie).
62
+ body.must_equal '{"counter"=>3}'
63
+ end
64
+
65
+ it "survives nonexistant cookies" do
66
+ pool = Rack::Session::Sequel.new(incrementor, db)
67
+ res = Rack::MockRequest.new(pool).
68
+ get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
69
+ res.body.must_equal '{"counter"=>1}'
70
+ end
71
+
72
+ it "does not send the same session id if it did not change" do
73
+ pool = Rack::Session::Sequel.new(incrementor, db)
74
+ req = Rack::MockRequest.new(pool)
75
+
76
+ res0 = req.get("/")
77
+ cookie = res0["Set-Cookie"][session_match]
78
+ res0.body.must_equal '{"counter"=>1}'
79
+ pool.table.count.must_equal 1
80
+
81
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
82
+ res1["Set-Cookie"].must_be_nil
83
+ res1.body.must_equal '{"counter"=>2}'
84
+ pool.table.count.must_equal 1
85
+
86
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
87
+ res2["Set-Cookie"].must_be_nil
88
+ res2.body.must_equal '{"counter"=>3}'
89
+ pool.table.count.must_equal 1
90
+ end
91
+
92
+ it "deletes cookies with :drop option" do
93
+ pool = Rack::Session::Sequel.new(incrementor, db)
94
+ req = Rack::MockRequest.new(pool)
95
+ drop = Rack::Utils::Context.new(pool, drop_session)
96
+ dreq = Rack::MockRequest.new(drop)
97
+
98
+ res1 = req.get("/")
99
+ session = (cookie = res1["Set-Cookie"])[session_match]
100
+ res1.body.must_equal '{"counter"=>1}'
101
+ pool.table.count.must_equal 1
102
+
103
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
104
+ res2["Set-Cookie"].must_be_nil
105
+ res2.body.must_equal '{"counter"=>2}'
106
+ pool.table.count.must_equal 0
107
+
108
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
109
+ res3["Set-Cookie"][session_match].wont_equal session
110
+ res3.body.must_equal '{"counter"=>1}'
111
+ pool.table.count.must_equal 1
112
+ end
113
+
114
+ it "provides new session id with :renew option" do
115
+ pool = Rack::Session::Sequel.new(incrementor, db)
116
+ req = Rack::MockRequest.new(pool)
117
+ renew = Rack::Utils::Context.new(pool, renew_session)
118
+ rreq = Rack::MockRequest.new(renew)
119
+
120
+ res1 = req.get("/")
121
+ session = (cookie = res1["Set-Cookie"])[session_match]
122
+ res1.body.must_equal '{"counter"=>1}'
123
+ pool.table.count.must_equal 1
124
+
125
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
126
+ new_cookie = res2["Set-Cookie"]
127
+ new_session = new_cookie[session_match]
128
+ new_session.wont_equal session
129
+ res2.body.must_equal '{"counter"=>2}'
130
+ pool.table.count.must_equal 1
131
+
132
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
133
+ res3.body.must_equal '{"counter"=>3}'
134
+ pool.table.count.must_equal 1
135
+
136
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
137
+ res4.body.must_equal '{"counter"=>1}'
138
+ pool.table.count.must_equal 2
139
+ end
140
+
141
+ it "omits cookie with :defer option" do
142
+ pool = Rack::Session::Sequel.new(incrementor, db)
143
+ defer = Rack::Utils::Context.new(pool, defer_session)
144
+ dreq = Rack::MockRequest.new(defer)
145
+
146
+ res1 = dreq.get("/")
147
+ res1["Set-Cookie"].must_be_nil
148
+ res1.body.must_equal '{"counter"=>1}'
149
+ pool.table.count.must_equal 1
150
+ end
151
+
152
+ # anyone know how to do this better?
153
+ it "should merge sessions when multithreaded" do
154
+ unless $DEBUG
155
+ 1.must_equal 1
156
+ next
157
+ end
158
+
159
+ warn 'Running multithread tests for Session::Pool'
160
+ pool = Rack::Session::Sequel.new(incrementor, db)
161
+ req = Rack::MockRequest.new(pool)
162
+
163
+ res = req.get('/')
164
+ res.body.must_equal '{"counter"=>1}'
165
+ cookie = res["Set-Cookie"]
166
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
167
+
168
+ delta_incrementor = lambda do |env|
169
+ # emulate disconjoinment of threading
170
+ env['rack.session'] = env['rack.session'].dup
171
+ Thread.stop
172
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
173
+ incrementor.call(env)
174
+ end
175
+ tses = Rack::Utils::Context.new pool, delta_incrementor
176
+ treq = Rack::MockRequest.new(tses)
177
+ tnum = rand(7).to_i+5
178
+ r = Array.new(tnum) do
179
+ Thread.new(treq) do |run|
180
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
181
+ end
182
+ end.reverse.map{|t| t.run.join.value }
183
+ r.each do |resp|
184
+ resp['Set-Cookie'].must_equal cookie
185
+ resp.body.must_include '"counter"=>2'
186
+ end
187
+
188
+ session = pool.pool[sess_id]
189
+ session.size.must_equal tnum+1 # counter
190
+ session['counter'].must_equal 2 # meeeh
191
+ end
192
+
193
+ it "does not return a cookie if cookie was not read/written" do
194
+ app = Rack::Session::Sequel.new(nothing, db)
195
+ res = Rack::MockRequest.new(app).get("/")
196
+ res["Set-Cookie"].must_be_nil
197
+ end
198
+
199
+ it "does not return a cookie if cookie was not written (only read)" do
200
+ app = Rack::Session::Sequel.new(session_id, db)
201
+ res = Rack::MockRequest.new(app).get("/")
202
+ res["Set-Cookie"].must_be_nil
203
+ end
204
+
205
+ it "returns even if not read/written if :expire_after is set" do
206
+ app = Rack::Session::Sequel.new(nothing, :expire_after => 3600, :db => db)
207
+ res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
208
+ res["Set-Cookie"].wont_be :nil?
209
+ end
210
+
211
+ it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
212
+ app = Rack::Session::Sequel.new(nothing, :expire_after => 3600, :db => db)
213
+ res = Rack::MockRequest.new(app).get("/")
214
+ res["Set-Cookie"].must_be_nil
215
+ end
216
+
217
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-sessions
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Tom Wardrop
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-16 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
+ - !ruby/object:Gem::Dependency
28
+ name: sequel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '5.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '5.1'
69
+ description: Sequel-based session middleware for Rack 2.0+
70
+ email: tom@tomwardrop.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - Gemfile
76
+ - LICENSE
77
+ - README.md
78
+ - lib/rack/session/sequel.rb
79
+ - lib/rack/session/version.rb
80
+ - lib/sequel-sessions.rb
81
+ - sequel-sessions.gemspec
82
+ - spec/sequel_sessions_spec.rb
83
+ homepage: https://github.com/wardrop/sequel-sessions
84
+ licenses:
85
+ - MIT
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 2.0.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 2.6.12
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Sequel-based session middleware for Rack 2.0+
107
+ test_files:
108
+ - spec/sequel_sessions_spec.rb