couchrest_session_store 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Gemfile +7 -0
- data/README.md +5 -1
- data/couchrest_session_store.gemspec +25 -0
- data/lib/couchrest/session/document.rb +76 -0
- data/lib/couchrest/session/store.rb +75 -0
- data/lib/couchrest/session/utility.rb +16 -0
- data/lib/couchrest/session.rb +5 -0
- data/lib/couchrest_session_store.rb +3 -114
- data/test/couch_tester.rb +26 -0
- data/test/session_store_test.rb +111 -0
- data/test/test_clock.rb +12 -0
- data/test/test_helper.rb +6 -0
- metadata +13 -2
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
# CouchRest
|
1
|
+
# CouchRest::Session::Store #
|
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
|
6
|
+
|
5
7
|
## Options ##
|
6
8
|
|
7
9
|
* marhal_data: (_defaults true_) - if set to false session data will be stored directly in the couch document. Otherwise it's marshalled and base64 encoded to enable restoring ruby data structures.
|
10
|
+
* database: database to use combined with config prefix and suffix
|
11
|
+
* exprire_after: livetime of a session in seconds
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# _*_ encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
|
5
|
+
gem.authors = ["Azul"]
|
6
|
+
gem.email = ["azul@leap.se"]
|
7
|
+
gem.summary = "A Rails Session Store based on CouchRest Model"
|
8
|
+
gem.description = gem.summary
|
9
|
+
gem.homepage = "http://github.com/azul/couchrest_session_store"
|
10
|
+
|
11
|
+
gem.has_rdoc = true
|
12
|
+
# gem.extra_rdoc_files = ["LICENSE"]
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.name = "couchrest_session_store"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = '0.2.0'
|
18
|
+
|
19
|
+
gem.add_dependency "couchrest"
|
20
|
+
gem.add_dependency "couchrest_model"
|
21
|
+
gem.add_dependency "actionpack"
|
22
|
+
|
23
|
+
gem.add_development_dependency "minitest"
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'couchrest/session/utility'
|
2
|
+
|
3
|
+
class CouchRest::Session::Document
|
4
|
+
include CouchRest::Session::Utility
|
5
|
+
|
6
|
+
def initialize(doc)
|
7
|
+
@doc = doc
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load(sid)
|
11
|
+
self.allocate.tap do |session_doc|
|
12
|
+
session_doc.load(sid)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.build(sid, session, options)
|
17
|
+
self.new(CouchRest::Document.new({"_id" => sid})).tap do |session_doc|
|
18
|
+
session_doc.update session, options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def load(sid)
|
23
|
+
@doc = database.get(sid)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_session
|
27
|
+
if doc["marshalled"]
|
28
|
+
session = unmarshal(doc["data"])
|
29
|
+
else
|
30
|
+
session = doc["data"]
|
31
|
+
end
|
32
|
+
return session
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete
|
36
|
+
database.delete_doc(doc)
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(session, options)
|
40
|
+
# clean up old data but leave id and revision intact
|
41
|
+
doc.reject! do |k,v|
|
42
|
+
k[0] != '_'
|
43
|
+
end
|
44
|
+
doc.merge! data_for_doc(session, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def save
|
48
|
+
database.save_doc(doc)
|
49
|
+
end
|
50
|
+
|
51
|
+
def expired?
|
52
|
+
expires && expires < Time.now
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def data_for_doc(session, options)
|
58
|
+
{ "data" => options[:marshal_data] ? marshal(session) : session,
|
59
|
+
"marshalled" => options[:marshal_data],
|
60
|
+
"expires" => expiry_from_options(options) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def expiry_from_options(options)
|
64
|
+
expire_after = options[:expire_after]
|
65
|
+
expire_after && (Time.now + expire_after)
|
66
|
+
end
|
67
|
+
|
68
|
+
def expires
|
69
|
+
doc["expires"] && Time.parse_iso8601(doc["expires"])
|
70
|
+
end
|
71
|
+
|
72
|
+
def doc
|
73
|
+
@doc
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class CouchRest::Session::Store < ActionDispatch::Session::AbstractStore
|
2
|
+
|
3
|
+
include CouchRest::Model::Configuration
|
4
|
+
include CouchRest::Model::Connection
|
5
|
+
|
6
|
+
def initialize(app, options = {})
|
7
|
+
super
|
8
|
+
self.class.set_options(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.set_options(options)
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# just fetch from the config
|
16
|
+
def self.database
|
17
|
+
@database ||= initialize_database
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.initialize_database
|
21
|
+
use_database @options[:database] || "sessions"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def get_session(env, sid)
|
27
|
+
if session = fetch_session(sid)
|
28
|
+
[sid, session]
|
29
|
+
else
|
30
|
+
[generate_sid, {}]
|
31
|
+
end
|
32
|
+
rescue RestClient::ResourceNotFound
|
33
|
+
# session data does not exist anymore
|
34
|
+
return [sid, {}]
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_session(env, sid, session, options)
|
38
|
+
doc = build_or_update_doc(sid, session, options)
|
39
|
+
doc.save
|
40
|
+
return sid
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy_session(env, sid, options)
|
44
|
+
doc = secure_get(sid)
|
45
|
+
doc.delete
|
46
|
+
generate_sid unless options[:drop]
|
47
|
+
rescue RestClient::ResourceNotFound
|
48
|
+
# already destroyed - we're done.
|
49
|
+
generate_sid unless options[:drop]
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_session(sid)
|
53
|
+
return nil unless sid
|
54
|
+
doc = secure_get(sid)
|
55
|
+
doc.to_session unless doc.expired?
|
56
|
+
end
|
57
|
+
|
58
|
+
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)
|
65
|
+
end
|
66
|
+
|
67
|
+
# prevent access to design docs
|
68
|
+
# this should be prevented on a couch permission level as well.
|
69
|
+
# but better be save than sorry.
|
70
|
+
def secure_get(sid)
|
71
|
+
raise RestClient::ResourceNotFound if /^_design\/(.*)/ =~ sid
|
72
|
+
CouchRest::Session::Document.load(sid)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CouchRest::Session::Utility
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def marshal(data)
|
5
|
+
::Base64.encode64(Marshal.dump(data)) if data
|
6
|
+
end
|
7
|
+
|
8
|
+
def unmarshal(data)
|
9
|
+
Marshal.load(::Base64.decode64(data)) if data
|
10
|
+
end
|
11
|
+
|
12
|
+
def database
|
13
|
+
CouchRest::Session::Store.database
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -4,118 +4,7 @@ require 'couchrest_model'
|
|
4
4
|
gem 'actionpack', '~> 3.0'
|
5
5
|
require 'action_dispatch'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
# Options:
|
12
|
-
# :database => database to use combined with config prefix and suffix
|
13
|
-
#
|
14
|
-
class CouchRestSessionStore < ActionDispatch::Session::AbstractStore
|
15
|
-
|
16
|
-
include CouchRest::Model::Configuration
|
17
|
-
include CouchRest::Model::Connection
|
18
|
-
|
19
|
-
class << self
|
20
|
-
def marshal(data)
|
21
|
-
::Base64.encode64(Marshal.dump(data)) if data
|
22
|
-
end
|
23
|
-
|
24
|
-
def unmarshal(data)
|
25
|
-
Marshal.load(::Base64.decode64(data)) if data
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def initialize(app, options = {})
|
31
|
-
super
|
32
|
-
self.class.set_options(options)
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.set_options(options)
|
36
|
-
@options = options
|
37
|
-
end
|
38
|
-
|
39
|
-
# just fetch from the config
|
40
|
-
def self.database
|
41
|
-
@database ||= initialize_database
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.initialize_database
|
45
|
-
use_database @options[:database] || "sessions"
|
46
|
-
end
|
47
|
-
|
48
|
-
def database
|
49
|
-
self.class.database
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def get_session(env, sid)
|
55
|
-
if sid
|
56
|
-
doc = secure_get(sid)
|
57
|
-
if doc["not_marshalled"]
|
58
|
-
session = doc.to_hash
|
59
|
-
session.delete("not_marshalled")
|
60
|
-
else
|
61
|
-
session = self.class.unmarshal(doc["data"])
|
62
|
-
end
|
63
|
-
[sid, session]
|
64
|
-
else
|
65
|
-
[generate_sid, {}]
|
66
|
-
end
|
67
|
-
rescue RestClient::ResourceNotFound
|
68
|
-
# session data does not exist anymore
|
69
|
-
return [sid, {}]
|
70
|
-
end
|
71
|
-
|
72
|
-
def set_session(env, sid, session, options)
|
73
|
-
doc = build_or_update_doc(sid, session, options[:marshal_data])
|
74
|
-
database.save_doc(doc)
|
75
|
-
return sid
|
76
|
-
end
|
77
|
-
|
78
|
-
def destroy_session(env, sid, options)
|
79
|
-
doc = secure_get(sid)
|
80
|
-
database.delete_doc(doc)
|
81
|
-
generate_sid unless options[:drop]
|
82
|
-
rescue RestClient::ResourceNotFound
|
83
|
-
# already destroyed - we're done.
|
84
|
-
generate_sid unless options[:drop]
|
85
|
-
end
|
86
|
-
|
87
|
-
def build_or_update_doc(sid, session, marshal_data)
|
88
|
-
marshal_data = true if marshal_data.nil?
|
89
|
-
doc = secure_get(sid)
|
90
|
-
update_doc doc, data_for_doc(session, marshal_data)
|
91
|
-
return doc
|
92
|
-
rescue RestClient::ResourceNotFound
|
93
|
-
data = data_for_doc(session, marshal_data).merge({"_id" => sid})
|
94
|
-
return CouchRest::Document.new(data)
|
95
|
-
end
|
96
|
-
|
97
|
-
def update_doc(doc, data)
|
98
|
-
# clean up old data but leave id and revision intact
|
99
|
-
doc.reject! do |k,v|
|
100
|
-
k[0] != '_'
|
101
|
-
end
|
102
|
-
doc.merge! data
|
103
|
-
end
|
104
|
-
|
105
|
-
def data_for_doc(session, marshal_data)
|
106
|
-
if marshal_data
|
107
|
-
{ "data" => self.class.marshal(session) }
|
108
|
-
else
|
109
|
-
session.merge({"not_marshalled" => true})
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# prevent access to design docs
|
114
|
-
# this should be prevented on a couch permission level as well.
|
115
|
-
# but better be save than sorry.
|
116
|
-
def secure_get(sid)
|
117
|
-
raise RestClient::ResourceNotFound if /^_design\/(.*)/ =~ sid
|
118
|
-
database.get(sid)
|
119
|
-
end
|
120
|
-
end
|
7
|
+
require 'couchrest/session'
|
8
|
+
require 'couchrest/session/store'
|
9
|
+
require 'couchrest/session/document'
|
121
10
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Access the couch directly so we can test its state without relying
|
3
|
+
# on the SessionStore
|
4
|
+
#
|
5
|
+
|
6
|
+
class CouchTester
|
7
|
+
include CouchRest::Model::Configuration
|
8
|
+
include CouchRest::Model::Connection
|
9
|
+
|
10
|
+
attr_reader :database
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@database = self.class.use_database options[:database] || "sessions"
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(sid)
|
17
|
+
database.get(sid)
|
18
|
+
end
|
19
|
+
|
20
|
+
def update(sid, diff)
|
21
|
+
doc = database.get(sid)
|
22
|
+
doc.merge! diff
|
23
|
+
database.save_doc(doc)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class SessionStoreTest < MiniTest::Test
|
4
|
+
|
5
|
+
def test_session_initialization
|
6
|
+
sid, session = store.send :get_session, env, nil
|
7
|
+
assert sid
|
8
|
+
assert_equal Hash.new, session
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_normal_session_flow
|
12
|
+
sid, session = init_session
|
13
|
+
store_session(sid, session)
|
14
|
+
assert_equal [sid, session], store.send(:get_session, env, sid)
|
15
|
+
store.send :destroy_session, env, sid, {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_updating_session
|
19
|
+
sid, session = init_session
|
20
|
+
store_session(sid, session)
|
21
|
+
session[:bla] = "blub"
|
22
|
+
store.send :set_session, env, sid, session, {}
|
23
|
+
assert_equal [sid, session], store.send(:get_session, env, sid)
|
24
|
+
store.send :destroy_session, env, sid, {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_unmarshalled_session_flow
|
28
|
+
sid, session = init_session
|
29
|
+
store_session sid, session, :marshal_data => false
|
30
|
+
new_sid, new_session = store.send(:get_session, env, sid)
|
31
|
+
assert_equal sid, new_sid
|
32
|
+
assert_equal session[:key], new_session["key"]
|
33
|
+
store.send :destroy_session, env, sid, {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_unmarshalled_data
|
37
|
+
sid, session = init_session
|
38
|
+
store_session sid, session, :marshal_data => false
|
39
|
+
couch = CouchTester.new
|
40
|
+
data = couch.get(sid)["data"]
|
41
|
+
assert_equal session[:key], data["key"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_logout_in_between
|
45
|
+
sid, session = init_session
|
46
|
+
store_session(sid, session)
|
47
|
+
store.send :destroy_session, env, sid, {}
|
48
|
+
other_sid, other_session = store.send(:get_session, env, sid)
|
49
|
+
assert_equal Hash.new, other_session
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_can_logout_twice
|
53
|
+
sid, session = init_session
|
54
|
+
store_session(sid, session)
|
55
|
+
store.send :destroy_session, env, sid, {}
|
56
|
+
store.send :destroy_session, env, sid, {}
|
57
|
+
other_sid, other_session = store.send(:get_session, env, sid)
|
58
|
+
assert_equal Hash.new, other_session
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_stored_and_not_expired_yet
|
62
|
+
sid, session = init_session
|
63
|
+
store_session(sid, session, expire_after: 300)
|
64
|
+
doc = CouchRest::Session::Document.load(sid)
|
65
|
+
expires = doc.send :expires
|
66
|
+
assert expires
|
67
|
+
assert !doc.expired?
|
68
|
+
assert (expires - Time.now) > 0, "Exiry should be in the future"
|
69
|
+
assert (expires - Time.now) <= 300, "Should expire after 300 seconds - not more"
|
70
|
+
assert_equal [sid, session], store.send(:get_session, env, sid)
|
71
|
+
end
|
72
|
+
|
73
|
+
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
|
+
other_sid, other_session = store.send(:get_session, env, sid)
|
78
|
+
assert_equal Hash.new, other_session, "session should have expired"
|
79
|
+
assert other_sid != sid
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_store_without_expiry
|
83
|
+
sid, session = init_session
|
84
|
+
store_session(sid, session)
|
85
|
+
couch = CouchTester.new
|
86
|
+
assert_nil couch.get(sid)["expires"]
|
87
|
+
assert_equal [sid, session], store.send(:get_session, env, sid)
|
88
|
+
end
|
89
|
+
|
90
|
+
def app
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def store(options = {})
|
95
|
+
@store ||= CouchRest::Session::Store.new(app, options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def env(settings = {})
|
99
|
+
env ||= settings
|
100
|
+
end
|
101
|
+
|
102
|
+
def init_session
|
103
|
+
sid, session = store.send :get_session, env, nil
|
104
|
+
session[:key] = "stub"
|
105
|
+
return sid, session
|
106
|
+
end
|
107
|
+
|
108
|
+
def store_session(sid, session, options = {})
|
109
|
+
store.send :set_session, env, sid, session, options
|
110
|
+
end
|
111
|
+
end
|
data/test/test_clock.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
gem 'minitest'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/couchrest_session_store.rb')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/couch_tester.rb')
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_clock.rb')
|
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.
|
4
|
+
version: 0.2.0
|
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-
|
12
|
+
date: 2013-09-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: couchrest
|
@@ -98,9 +98,20 @@ executables: []
|
|
98
98
|
extensions: []
|
99
99
|
extra_rdoc_files: []
|
100
100
|
files:
|
101
|
+
- .travis.yml
|
102
|
+
- Gemfile
|
101
103
|
- README.md
|
102
104
|
- Rakefile
|
105
|
+
- couchrest_session_store.gemspec
|
106
|
+
- lib/couchrest/session.rb
|
107
|
+
- lib/couchrest/session/document.rb
|
108
|
+
- lib/couchrest/session/store.rb
|
109
|
+
- lib/couchrest/session/utility.rb
|
103
110
|
- lib/couchrest_session_store.rb
|
111
|
+
- test/couch_tester.rb
|
112
|
+
- test/session_store_test.rb
|
113
|
+
- test/test_clock.rb
|
114
|
+
- test/test_helper.rb
|
104
115
|
homepage: http://github.com/azul/couchrest_session_store
|
105
116
|
licenses: []
|
106
117
|
post_install_message:
|