couchrest_session_store 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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