couchrest_session_store 0.2.0 → 0.2.1

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.
data/.travis.yml CHANGED
@@ -2,3 +2,5 @@ services:
2
2
  - couchdb
3
3
  notifications:
4
4
  email: false
5
+ before_script:
6
+ - "test/setup_couch.sh"
data/README.md CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  A simple session store based on CouchRest Model.
4
4
 
5
- It will automatically pick up the config/couch.yml file for CouchRest Model
5
+
6
+ ## Setup ##
7
+
8
+ CouchRest::Session::Store will automatically pick up the config/couch.yml file for CouchRest Model.
9
+ Cleaning up sessions requires a design document in the sessions database that enables querying by expiry. See the design directory for an example and test/setup_couch.sh for a script that puts the document on the couch for our tests.
10
+
11
+
6
12
 
7
13
  ## Options ##
8
14
 
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.files = `git ls-files`.split("\n")
15
15
  gem.name = "couchrest_session_store"
16
16
  gem.require_paths = ["lib"]
17
- gem.version = '0.2.0'
17
+ gem.version = '0.2.1'
18
18
 
19
19
  gem.add_dependency "couchrest"
20
20
  gem.add_dependency "couchrest_model"
@@ -0,0 +1,8 @@
1
+ {
2
+ "views": {
3
+ "by_expires": {
4
+ "reduce": "_sum",
5
+ "map": "function(doc) {\n if(typeof doc.expires !== \"undefined\") {\n emit(doc.expires, 1);\n }\n}\n"
6
+ }
7
+ }
8
+ }
@@ -1,4 +1,5 @@
1
1
  require 'couchrest/session/utility'
2
+ require 'time'
2
3
 
3
4
  class CouchRest::Session::Document
4
5
  include CouchRest::Session::Utility
@@ -13,12 +14,21 @@ class CouchRest::Session::Document
13
14
  end
14
15
  end
15
16
 
16
- def self.build(sid, session, options)
17
+ def self.build(sid, session, options = {})
17
18
  self.new(CouchRest::Document.new({"_id" => sid})).tap do |session_doc|
18
19
  session_doc.update session, options
19
20
  end
20
21
  end
21
22
 
23
+ def self.build_or_update(sid, session, options = {})
24
+ options[:marshal_data] = true if options[:marshal_data].nil?
25
+ doc = self.load(sid)
26
+ doc.update(session, options)
27
+ return doc
28
+ rescue RestClient::ResourceNotFound
29
+ self.build(sid, session, options)
30
+ end
31
+
22
32
  def load(sid)
23
33
  @doc = database.get(sid)
24
34
  end
@@ -62,11 +72,11 @@ class CouchRest::Session::Document
62
72
 
63
73
  def expiry_from_options(options)
64
74
  expire_after = options[:expire_after]
65
- expire_after && (Time.now + expire_after)
75
+ expire_after && (Time.now + expire_after).utc
66
76
  end
67
77
 
68
78
  def expires
69
- doc["expires"] && Time.parse_iso8601(doc["expires"])
79
+ doc["expires"] && Time.iso8601(doc["expires"])
70
80
  end
71
81
 
72
82
  def doc
@@ -21,6 +21,29 @@ class CouchRest::Session::Store < ActionDispatch::Session::AbstractStore
21
21
  use_database @options[:database] || "sessions"
22
22
  end
23
23
 
24
+ def cleanup(rows)
25
+ rows.each do |row|
26
+ doc = CouchRest::Session::Document.load(row['id'])
27
+ doc.delete
28
+ end
29
+ end
30
+
31
+ def expired
32
+ find_by_expires startkey: 1,
33
+ endkey: Time.now.utc.iso8601
34
+ end
35
+
36
+ def never_expiring
37
+ find_by_expires endkey: 1
38
+ end
39
+
40
+ def find_by_expires(options = {})
41
+ options[:reduce] ||= false
42
+ design = self.class.database.get '_design/Session'
43
+ response = design.view :by_expires, options
44
+ response['rows']
45
+ end
46
+
24
47
  private
25
48
 
26
49
  def get_session(env, sid)
@@ -35,6 +58,7 @@ class CouchRest::Session::Store < ActionDispatch::Session::AbstractStore
35
58
  end
36
59
 
37
60
  def set_session(env, sid, session, options)
61
+ raise RestClient::ResourceNotFound if /^_design\/(.*)/ =~ sid
38
62
  doc = build_or_update_doc(sid, session, options)
39
63
  doc.save
40
64
  return sid
@@ -56,12 +80,7 @@ class CouchRest::Session::Store < ActionDispatch::Session::AbstractStore
56
80
  end
57
81
 
58
82
  def build_or_update_doc(sid, session, options)
59
- options[:marshal_data] = true if options[:marshal_data].nil?
60
- doc = secure_get(sid)
61
- doc.update(session, options)
62
- return doc
63
- rescue RestClient::ResourceNotFound
64
- CouchRest::Session::Document.build(sid, session, options)
83
+ CouchRest::Session::Document.build_or_update(sid, session, options)
65
84
  end
66
85
 
67
86
  # prevent access to design docs
@@ -71,5 +90,5 @@ class CouchRest::Session::Store < ActionDispatch::Session::AbstractStore
71
90
  raise RestClient::ResourceNotFound if /^_design\/(.*)/ =~ sid
72
91
  CouchRest::Session::Document.load(sid)
73
92
  end
74
- end
75
93
 
94
+ end
@@ -9,21 +9,27 @@ class SessionStoreTest < MiniTest::Test
9
9
  end
10
10
 
11
11
  def test_normal_session_flow
12
- sid, session = init_session
13
- store_session(sid, session)
12
+ sid, session = never_expiring_session
14
13
  assert_equal [sid, session], store.send(:get_session, env, sid)
15
14
  store.send :destroy_session, env, sid, {}
16
15
  end
17
16
 
18
17
  def test_updating_session
19
- sid, session = init_session
20
- store_session(sid, session)
18
+ sid, session = never_expiring_session
21
19
  session[:bla] = "blub"
22
20
  store.send :set_session, env, sid, session, {}
23
21
  assert_equal [sid, session], store.send(:get_session, env, sid)
24
22
  store.send :destroy_session, env, sid, {}
25
23
  end
26
24
 
25
+ def test_prevent_access_to_design_docs
26
+ sid = '_design/bla'
27
+ session = {views: 'my hacked view'}
28
+ assert_raises RestClient::ResourceNotFound do
29
+ store_session(sid, session)
30
+ end
31
+ end
32
+
27
33
  def test_unmarshalled_session_flow
28
34
  sid, session = init_session
29
35
  store_session sid, session, :marshal_data => false
@@ -42,16 +48,14 @@ class SessionStoreTest < MiniTest::Test
42
48
  end
43
49
 
44
50
  def test_logout_in_between
45
- sid, session = init_session
46
- store_session(sid, session)
51
+ sid, session = never_expiring_session
47
52
  store.send :destroy_session, env, sid, {}
48
53
  other_sid, other_session = store.send(:get_session, env, sid)
49
54
  assert_equal Hash.new, other_session
50
55
  end
51
56
 
52
57
  def test_can_logout_twice
53
- sid, session = init_session
54
- store_session(sid, session)
58
+ sid, session = never_expiring_session
55
59
  store.send :destroy_session, env, sid, {}
56
60
  store.send :destroy_session, env, sid, {}
57
61
  other_sid, other_session = store.send(:get_session, env, sid)
@@ -59,8 +63,7 @@ class SessionStoreTest < MiniTest::Test
59
63
  end
60
64
 
61
65
  def test_stored_and_not_expired_yet
62
- sid, session = init_session
63
- store_session(sid, session, expire_after: 300)
66
+ sid, session = expiring_session
64
67
  doc = CouchRest::Session::Document.load(sid)
65
68
  expires = doc.send :expires
66
69
  assert expires
@@ -71,17 +74,44 @@ class SessionStoreTest < MiniTest::Test
71
74
  end
72
75
 
73
76
  def test_stored_but_expired
74
- sid, session = init_session
75
- store_session(sid, session, expire_after: 300)
76
- CouchTester.new.update(sid, "expires" => Time.now - 2.minutes)
77
+ sid, session = expired_session
77
78
  other_sid, other_session = store.send(:get_session, env, sid)
78
79
  assert_equal Hash.new, other_session, "session should have expired"
79
80
  assert other_sid != sid
80
81
  end
81
82
 
83
+ def test_find_expired_sessions
84
+ expired, expiring, never_expiring = seed_sessions
85
+ expired_session_ids = store.expired.map {|row| row['id']}
86
+ assert expired_session_ids.include?(expired)
87
+ assert !expired_session_ids.include?(expiring)
88
+ assert !expired_session_ids.include?(never_expiring)
89
+ end
90
+
91
+ def test_find_never_expiring_sessions
92
+ expired, expiring, never_expiring = seed_sessions
93
+ never_expiring_session_ids = store.never_expiring.map {|row| row['id']}
94
+ assert never_expiring_session_ids.include?(never_expiring)
95
+ assert !never_expiring_session_ids.include?(expiring)
96
+ assert !never_expiring_session_ids.include?(expired)
97
+ end
98
+
99
+ def test_cleanup_expired_sessions
100
+ sid, session = expired_session
101
+ store.cleanup(store.expired)
102
+ assert_raises RestClient::ResourceNotFound do
103
+ CouchTester.new.get(sid)
104
+ end
105
+ end
106
+
107
+ def test_keep_fresh_during_cleanup
108
+ sid, session = expiring_session
109
+ store.cleanup(store.expired)
110
+ assert_equal [sid, session], store.send(:get_session, env, sid)
111
+ end
112
+
82
113
  def test_store_without_expiry
83
- sid, session = init_session
84
- store_session(sid, session)
114
+ sid, session = never_expiring_session
85
115
  couch = CouchTester.new
86
116
  assert_nil couch.get(sid)["expires"]
87
117
  assert_equal [sid, session], store.send(:get_session, env, sid)
@@ -99,6 +129,25 @@ class SessionStoreTest < MiniTest::Test
99
129
  env ||= settings
100
130
  end
101
131
 
132
+ # returns the session ids of an expired, and expiring and a never
133
+ # expiring session
134
+ def seed_sessions
135
+ [expired_session, expiring_session, never_expiring_session].map(&:first)
136
+ end
137
+
138
+ def never_expiring_session
139
+ store_session *init_session
140
+ end
141
+
142
+ def expiring_session
143
+ sid, session = init_session
144
+ store_session(sid, session, expire_after: 300)
145
+ end
146
+
147
+ def expired_session
148
+ expire_session *expiring_session
149
+ end
150
+
102
151
  def init_session
103
152
  sid, session = store.send :get_session, env, nil
104
153
  session[:key] = "stub"
@@ -107,5 +156,13 @@ class SessionStoreTest < MiniTest::Test
107
156
 
108
157
  def store_session(sid, session, options = {})
109
158
  store.send :set_session, env, sid, session, options
159
+ return sid, session
110
160
  end
161
+
162
+ def expire_session(sid, session)
163
+ CouchTester.new.update sid,
164
+ "expires" => (Time.now - 10.minutes).utc.iso8601
165
+ return sid, session
166
+ end
167
+
111
168
  end
@@ -0,0 +1,7 @@
1
+ HOST="http://localhost:5984"
2
+ echo "couch version :"
3
+ curl -X GET $HOST
4
+
5
+ curl -X PUT $HOST/couchrest_sessions
6
+ curl -X PUT $HOST/couchrest_sessions/_design/Session --data @design/Session.json
7
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchrest_session_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-09-17 00:00:00.000000000 Z
12
+ date: 2013-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: couchrest
@@ -103,6 +103,7 @@ files:
103
103
  - README.md
104
104
  - Rakefile
105
105
  - couchrest_session_store.gemspec
106
+ - design/Session.json
106
107
  - lib/couchrest/session.rb
107
108
  - lib/couchrest/session/document.rb
108
109
  - lib/couchrest/session/store.rb
@@ -110,6 +111,7 @@ files:
110
111
  - lib/couchrest_session_store.rb
111
112
  - test/couch_tester.rb
112
113
  - test/session_store_test.rb
114
+ - test/setup_couch.sh
113
115
  - test/test_clock.rb
114
116
  - test/test_helper.rb
115
117
  homepage: http://github.com/azul/couchrest_session_store