strelka 0.0.1.pre.187 → 0.0.1.pre.193
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.tar.gz.sig +0 -0
- data/ChangeLog +94 -26
- data/Manifest.txt +4 -2
- data/examples/apps/ws-echo +17 -0
- data/lib/strelka/app.rb +26 -24
- data/lib/strelka/app/auth.rb +2 -1
- data/lib/strelka/app/errors.rb +1 -1
- data/lib/strelka/app/filters.rb +1 -1
- data/lib/strelka/app/negotiation.rb +1 -1
- data/lib/strelka/app/parameters.rb +2 -2
- data/lib/strelka/app/restresources.rb +1 -1
- data/lib/strelka/app/routing.rb +3 -2
- data/lib/strelka/app/sessions.rb +3 -3
- data/lib/strelka/app/templating.rb +3 -3
- data/lib/strelka/authprovider.rb +2 -10
- data/lib/strelka/behavior/plugin.rb +3 -3
- data/lib/strelka/httprequest.rb +5 -2
- data/lib/strelka/httprequest/session.rb +3 -2
- data/lib/strelka/httpresponse/session.rb +8 -9
- data/lib/strelka/mixins.rb +15 -0
- data/lib/strelka/plugins.rb +257 -0
- data/lib/strelka/router/default.rb +27 -2
- data/lib/strelka/session.rb +20 -2
- data/lib/strelka/session/db.rb +20 -10
- data/lib/strelka/session/default.rb +41 -18
- data/spec/lib/helpers.rb +1 -1
- data/spec/strelka/app/auth_spec.rb +1 -1
- data/spec/strelka/app/errors_spec.rb +1 -1
- data/spec/strelka/app/filters_spec.rb +1 -1
- data/spec/strelka/app/negotiation_spec.rb +1 -1
- data/spec/strelka/app/parameters_spec.rb +1 -1
- data/spec/strelka/app/restresources_spec.rb +1 -1
- data/spec/strelka/app/routing_spec.rb +4 -1
- data/spec/strelka/app/sessions_spec.rb +63 -17
- data/spec/strelka/app/templating_spec.rb +1 -1
- data/spec/strelka/app_spec.rb +13 -5
- data/spec/strelka/httprequest/session_spec.rb +44 -23
- data/spec/strelka/httprequest_spec.rb +21 -0
- data/spec/strelka/httpresponse/session_spec.rb +143 -0
- data/spec/strelka/{app/plugins_spec.rb → plugins_spec.rb} +64 -53
- data/spec/strelka/router/default_spec.rb +15 -0
- data/spec/strelka/router/exclusive_spec.rb +14 -0
- data/spec/strelka/session/db_spec.rb +11 -0
- data/spec/strelka/session/default_spec.rb +10 -2
- metadata +119 -37
- metadata.gz.sig +0 -0
- data/lib/strelka/app/plugins.rb +0 -284
data/lib/strelka/session.rb
CHANGED
@@ -61,6 +61,14 @@ class Strelka::Session
|
|
61
61
|
end
|
62
62
|
|
63
63
|
|
64
|
+
### Fetch the session ID from the given +request, returning +nil+ if it doesn't
|
65
|
+
### have any session attributes. You should override this and provide the
|
66
|
+
### code which fetches the ID from the +request+.
|
67
|
+
def self::get_existing_session_id( request )
|
68
|
+
return nil
|
69
|
+
end
|
70
|
+
|
71
|
+
|
64
72
|
### Fetch the session ID from the given +request+, or create a new one if the
|
65
73
|
### request is +nil+ or doesn't have the necessary attributes. You should
|
66
74
|
### override this, as the default implementation just returns +nil+.
|
@@ -106,13 +114,23 @@ class Strelka::Session
|
|
106
114
|
end
|
107
115
|
|
108
116
|
|
117
|
+
### Return +true+ if the given +request+ has a valid session token, and that
|
118
|
+
### token corresponds to an existing session ID. You should override this if
|
119
|
+
### there's a cheaper way to check for an existing session than just calling
|
120
|
+
### ::load_session_data.
|
121
|
+
def self::has_session_for?( request )
|
122
|
+
id = self.get_existing_session_id( request ) or return false
|
123
|
+
return true if self.load( id )
|
124
|
+
end
|
125
|
+
|
126
|
+
|
109
127
|
#################################################################
|
110
128
|
### I N S T A N C E M E T H O D S
|
111
129
|
#################################################################
|
112
130
|
|
113
131
|
### Set up a new instance with the given +session_id+ and +initial_values+.
|
114
|
-
def initialize( session_id, initial_values={} ) # :notnew:
|
115
|
-
@session_id = session_id
|
132
|
+
def initialize( session_id=nil, initial_values={} ) # :notnew:
|
133
|
+
@session_id = session_id || self.class.get_session_id
|
116
134
|
end
|
117
135
|
|
118
136
|
|
data/lib/strelka/session/db.rb
CHANGED
@@ -15,7 +15,8 @@ require 'strelka/session/default'
|
|
15
15
|
#
|
16
16
|
class Strelka::Session::Db < Strelka::Session::Default
|
17
17
|
include Strelka::Loggable
|
18
|
-
extend Forwardable
|
18
|
+
extend Forwardable,
|
19
|
+
Strelka::MethodUtilities
|
19
20
|
|
20
21
|
# Class-instance variables
|
21
22
|
@table_name = :sessions
|
@@ -25,16 +26,17 @@ class Strelka::Session::Db < Strelka::Session::Default
|
|
25
26
|
:name => 'strelka-session'
|
26
27
|
}
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
##
|
30
|
+
# The Sequel dataset connection
|
31
|
+
singleton_attr_reader :db
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
##
|
34
|
+
# The Sequel dataset for the sessions table
|
35
|
+
singleton_attr_reader :dataset
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
##
|
38
|
+
# The configured session cookie parameters
|
39
|
+
singleton_attr_accessor :cookie_options
|
38
40
|
|
39
41
|
|
40
42
|
########################################################################
|
@@ -94,7 +96,7 @@ class Strelka::Session::Db < Strelka::Session::Default
|
|
94
96
|
|
95
97
|
|
96
98
|
### Save the given +data+ associated with the +session_id+ to the DB.
|
97
|
-
def self::save_session_data( session_id, data )
|
99
|
+
def self::save_session_data( session_id, data={} )
|
98
100
|
self.db.transaction do
|
99
101
|
self.delete_session_data( session_id.to_s )
|
100
102
|
self.dataset.insert(
|
@@ -111,5 +113,13 @@ class Strelka::Session::Db < Strelka::Session::Default
|
|
111
113
|
self.dataset.filter( :session_id => session_id ).delete
|
112
114
|
end
|
113
115
|
|
116
|
+
|
117
|
+
### Return +true+ if the given +request+ has a session token which corresponds
|
118
|
+
### to an existing session key.
|
119
|
+
def self::has_session_for?( request )
|
120
|
+
id = self.get_existing_session_id( request ) or return false
|
121
|
+
return !self.dataset.filter( :session_id => id ).empty?
|
122
|
+
end
|
123
|
+
|
114
124
|
end # class Strelka::Session::Db
|
115
125
|
|
@@ -18,31 +18,38 @@ require 'strelka/mixins'
|
|
18
18
|
# #[], #[]=, #delete, #key?
|
19
19
|
class Strelka::Session::Default < Strelka::Session
|
20
20
|
extend Forwardable,
|
21
|
+
Strelka::MethodUtilities,
|
21
22
|
Strelka::Delegation
|
22
23
|
include Strelka::Loggable,
|
23
24
|
Strelka::DataUtilities
|
24
25
|
|
26
|
+
# Default configuration
|
27
|
+
DEFAULT_COOKIE_OPTIONS = {
|
28
|
+
:name => 'strelka-session'
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
|
25
32
|
# Class-instance variables
|
33
|
+
@cookie_options = DEFAULT_COOKIE_OPTIONS.dup
|
26
34
|
@sessions = {}
|
27
|
-
@cookie_options = {
|
28
|
-
:name => 'strelka-session'
|
29
|
-
}
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
36
|
+
##
|
37
|
+
# In-memory session store
|
38
|
+
singleton_attr_reader :sessions
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
##
|
41
|
+
# The configured session cookie parameters
|
42
|
+
singleton_attr_accessor :cookie_options
|
38
43
|
|
39
44
|
|
40
45
|
### Configure the session class with the given +options+, which should be a
|
41
46
|
### Hash or an object that has a Hash-like interface. Sets cookie options
|
42
47
|
### for the session if the +:cookie+ key is set.
|
43
|
-
def self::configure( options )
|
48
|
+
def self::configure( options=nil )
|
44
49
|
if options
|
45
50
|
self.cookie_options.merge!( options[:cookie] ) if options[:cookie]
|
51
|
+
else
|
52
|
+
self.cookie_options = DEFAULT_COOKIE_OPTIONS.dup
|
46
53
|
end
|
47
54
|
end
|
48
55
|
|
@@ -66,17 +73,35 @@ class Strelka::Session::Default < Strelka::Session
|
|
66
73
|
end
|
67
74
|
|
68
75
|
|
76
|
+
### Try to fetch a session ID from the specified +request+, returning +nil+
|
77
|
+
### if there isn't one.
|
78
|
+
def self::get_existing_session_id( request )
|
79
|
+
cookie = request.cookies[ self.cookie_options[:name] ] or return nil
|
80
|
+
|
81
|
+
if cookie.value =~ /^([[:xdigit:]]+)$/i
|
82
|
+
return $1.untaint
|
83
|
+
else
|
84
|
+
Strelka.log.warn "Request with a malformed session cookie: %p" % [ request ]
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
69
90
|
### Fetch the session ID from the given +request+, or create a new one if the
|
70
91
|
### request doesn't have the necessary attributes.
|
71
92
|
def self::get_session_id( request=nil )
|
72
|
-
id =
|
93
|
+
id = self.get_existing_session_id( request ) if request
|
94
|
+
return id || SecureRandom.hex
|
95
|
+
end
|
73
96
|
|
74
|
-
# Fetch and untaint the existing ID if it exists and looks valid
|
75
|
-
if request && (cookie = request.cookies[ self.cookie_options[:name] ])
|
76
|
-
id = $1.untaint if cookie.value =~ /^([[:xdigit:]]+)$/i
|
77
|
-
end
|
78
97
|
|
79
|
-
|
98
|
+
### Return +true+ if the given +request+ has a session token which corresponds
|
99
|
+
### to an existing session key.
|
100
|
+
def self::has_session_for?( request )
|
101
|
+
Strelka.log.debug "Checking request (%s/%d) for session." % [ request.sender_id, request.conn_id ]
|
102
|
+
id = self.get_existing_session_id( request ) or return false
|
103
|
+
Strelka.log.debug " got a session ID: %p" % [ id ]
|
104
|
+
return @sessions.key?( id )
|
80
105
|
end
|
81
106
|
|
82
107
|
|
@@ -86,8 +111,6 @@ class Strelka::Session::Default < Strelka::Session
|
|
86
111
|
|
87
112
|
### Create a new session store using the given +hash+ for initial values.
|
88
113
|
def initialize( session_id=nil, initial_values={} )
|
89
|
-
session_id ||= self.class.get_session_id
|
90
|
-
|
91
114
|
@hash = Hash.new {|h,k| h[k] = {} }
|
92
115
|
@hash.merge!( initial_values )
|
93
116
|
|
data/spec/lib/helpers.rb
CHANGED
@@ -13,7 +13,7 @@ require 'rspec'
|
|
13
13
|
require 'spec/lib/helpers'
|
14
14
|
|
15
15
|
require 'strelka'
|
16
|
-
require 'strelka/
|
16
|
+
require 'strelka/plugins'
|
17
17
|
require 'strelka/app/routing'
|
18
18
|
|
19
19
|
require 'strelka/behavior/plugin'
|
@@ -203,6 +203,7 @@ describe Strelka::App::Routing do
|
|
203
203
|
|
204
204
|
it "has its routes inherited by subclasses" do
|
205
205
|
@app.class_eval do
|
206
|
+
router :deep
|
206
207
|
get( '/info' ) {}
|
207
208
|
get( '/about' ) {}
|
208
209
|
get( '/origami' ) {}
|
@@ -220,6 +221,8 @@ describe Strelka::App::Routing do
|
|
220
221
|
subclass.routes.should include(
|
221
222
|
[ :GET, ['origami'], {action: subclass.instance_method(:GET_origami), options: {}} ]
|
222
223
|
)
|
224
|
+
|
225
|
+
subclass.routerclass.should == @app.routerclass
|
223
226
|
end
|
224
227
|
|
225
228
|
describe "that also uses the :parameters plugin" do
|
@@ -11,7 +11,7 @@ require 'rspec'
|
|
11
11
|
require 'spec/lib/helpers'
|
12
12
|
|
13
13
|
require 'strelka'
|
14
|
-
require 'strelka/
|
14
|
+
require 'strelka/plugins'
|
15
15
|
require 'strelka/app/sessions'
|
16
16
|
|
17
17
|
require 'strelka/behavior/plugin'
|
@@ -36,23 +36,50 @@ describe Strelka::App::Sessions do
|
|
36
36
|
it_should_behave_like( "A Strelka::App Plugin" )
|
37
37
|
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
describe "session-class loading" do
|
40
|
+
before( :all ) do
|
41
|
+
# First, hook the anonymous class up to the 'testing' name using the PluginFactory API
|
42
|
+
@test_session_class = Class.new( Strelka::Session ) do
|
43
|
+
class << self; attr_accessor :options; end
|
44
|
+
def self::configure( options )
|
45
|
+
@options = options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
Strelka::Session.derivatives[ 'testing' ] = @test_session_class
|
49
|
+
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
Strelka::Session.derivatives[ 'testing' ] = test_session_class
|
51
|
+
after( :each ) do
|
52
|
+
Strelka::App::Sessions.instance_variable_set( :@session_class, nil )
|
53
|
+
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
it "has a default associated session class" do
|
56
|
+
Strelka::App::Sessions.session_class.should be_a( Class )
|
57
|
+
Strelka::App::Sessions.session_class.should < Strelka::Session
|
58
|
+
end
|
52
59
|
|
53
|
-
|
54
|
-
|
60
|
+
it "is can be configured to use a different session class" do
|
61
|
+
Strelka::App::Sessions.configure( :session_class => 'testing' )
|
62
|
+
Strelka::App::Sessions.session_class.should == @test_session_class
|
63
|
+
end
|
55
64
|
|
65
|
+
it "configures the configured session class with default options" do
|
66
|
+
Strelka::App::Sessions.configure( :session_class => 'testing' )
|
67
|
+
Strelka::App::Sessions.session_class.options.should == Strelka::App::Sessions::DEFAULT_OPTIONS
|
68
|
+
end
|
69
|
+
|
70
|
+
it "merges any config options for the configured session class" do
|
71
|
+
options = { 'cookie_name' => 'patience' }
|
72
|
+
Strelka::App::Sessions.configure( :session_class => 'testing', :options => options )
|
73
|
+
Strelka::App::Sessions.session_class.options.
|
74
|
+
should == Strelka::App::Sessions::DEFAULT_OPTIONS.merge( options )
|
75
|
+
end
|
76
|
+
|
77
|
+
it "uses the default session class if the config doesn't have a session section" do
|
78
|
+
Strelka::App::Sessions.configure
|
79
|
+
Strelka::App::Sessions.session_class.should be( Strelka::Session::Default )
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
56
83
|
|
57
84
|
describe "an including App" do
|
58
85
|
|
@@ -72,9 +99,11 @@ describe Strelka::App::Sessions do
|
|
72
99
|
super
|
73
100
|
end
|
74
101
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
102
|
+
def handle_request( req )
|
103
|
+
super do
|
104
|
+
req.session[ :test ] = 'session data'
|
105
|
+
req.response
|
106
|
+
end
|
78
107
|
end
|
79
108
|
end
|
80
109
|
end
|
@@ -107,6 +136,23 @@ describe Strelka::App::Sessions do
|
|
107
136
|
@app.session_namespace.should == :findizzle
|
108
137
|
end
|
109
138
|
|
139
|
+
it "extends the request and response classes" do
|
140
|
+
@app.install_plugins
|
141
|
+
Strelka::HTTPRequest.should < Strelka::HTTPRequest::Session
|
142
|
+
Strelka::HTTPResponse.should < Strelka::HTTPResponse::Session
|
143
|
+
end
|
144
|
+
|
145
|
+
it "sets the session namespace on requests" do
|
146
|
+
req = @request_factory.get( '/foom' )
|
147
|
+
res = @app.new.handle( req )
|
148
|
+
req.session_namespace.should == @app.default_appid
|
149
|
+
end
|
150
|
+
|
151
|
+
it "saves the session automatically" do
|
152
|
+
req = @request_factory.get( '/foom' )
|
153
|
+
res = @app.new.handle( req )
|
154
|
+
res.cookies.should include( Strelka::Session::Default.cookie_options[:name] )
|
155
|
+
end
|
110
156
|
|
111
157
|
end
|
112
158
|
|
data/spec/strelka/app_spec.rb
CHANGED
@@ -26,6 +26,7 @@ describe Strelka::App do
|
|
26
26
|
|
27
27
|
before( :all ) do
|
28
28
|
setup_logging( :fatal )
|
29
|
+
@initial_registry = Strelka::App.loaded_plugins.dup
|
29
30
|
@request_factory = Mongrel2::RequestFactory.new( route: '/mail' )
|
30
31
|
Mongrel2::Config.db = Mongrel2::Config.in_memory_db
|
31
32
|
Mongrel2::Config.init_database
|
@@ -39,6 +40,7 @@ describe Strelka::App do
|
|
39
40
|
end
|
40
41
|
|
41
42
|
before( :each ) do
|
43
|
+
Strelka::App.loaded_plugins.clear
|
42
44
|
@app = Class.new( Strelka::App ) do
|
43
45
|
def initialize( appid=TEST_APPID, sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
|
44
46
|
super
|
@@ -55,6 +57,7 @@ describe Strelka::App do
|
|
55
57
|
end
|
56
58
|
|
57
59
|
after( :all ) do
|
60
|
+
Strelka::App.loaded_plugins = @initial_registry
|
58
61
|
reset_logging()
|
59
62
|
end
|
60
63
|
|
@@ -174,7 +177,8 @@ describe Strelka::App do
|
|
174
177
|
|
175
178
|
# make a plugin that always 304s and install it
|
176
179
|
not_modified_plugin = Module.new do
|
177
|
-
|
180
|
+
def self::name; "Strelka::App::NotModified"; end
|
181
|
+
extend Strelka::Plugin
|
178
182
|
def handle_request( r )
|
179
183
|
finish_with( HTTP::NOT_MODIFIED, "Unchanged." )
|
180
184
|
fail "Shouldn't be reached."
|
@@ -193,7 +197,8 @@ describe Strelka::App do
|
|
193
197
|
it "creates a simple response body for status responses that can have them" do
|
194
198
|
# make an auth plugin that always denies requests
|
195
199
|
forbidden_plugin = Module.new do
|
196
|
-
|
200
|
+
def self::name; "Strelka::App::Forbidden"; end
|
201
|
+
extend Strelka::Plugin
|
197
202
|
def handle_request( r )
|
198
203
|
finish_with( HTTP::FORBIDDEN, "You aren't allowed to look at that." )
|
199
204
|
fail "Shouldn't be reached."
|
@@ -213,7 +218,8 @@ describe Strelka::App do
|
|
213
218
|
it "uses the specified content type for error responses" do
|
214
219
|
# make an auth plugin that always denies requests
|
215
220
|
forbidden_plugin = Module.new do
|
216
|
-
|
221
|
+
def self::name; "Strelka::App::Forbidden"; end
|
222
|
+
extend Strelka::Plugin
|
217
223
|
def handle_request( r )
|
218
224
|
finish_with( HTTP::FORBIDDEN, "You aren't allowed to look at that.",
|
219
225
|
:content_type => 'text/html' )
|
@@ -350,7 +356,8 @@ describe Strelka::App do
|
|
350
356
|
it "provides a plugin hook for plugins to manipulate the request before handling it" do
|
351
357
|
# make a fixup plugin that adds a custom x- header to the request
|
352
358
|
header_fixup_plugin = Module.new do
|
353
|
-
|
359
|
+
def self::name; "Strelka::App::HeaderFixup"; end
|
360
|
+
extend Strelka::Plugin
|
354
361
|
def fixup_request( r )
|
355
362
|
r.headers[:x_funted_by] = 'Cragnux/1.1.3'
|
356
363
|
super
|
@@ -375,7 +382,8 @@ describe Strelka::App do
|
|
375
382
|
it "provides a plugin hook for plugins to manipulate the response before it's returned to Mongrel2" do
|
376
383
|
# make a fixup plugin that adds a custom x- header to the response
|
377
384
|
header_fixup_plugin = Module.new do
|
378
|
-
|
385
|
+
def self::name; "Strelka::App::HeaderFixup"; end
|
386
|
+
extend Strelka::Plugin
|
379
387
|
def fixup_response( res )
|
380
388
|
res.headers.x_funted_by = 'Cragnux/1.1.3'
|
381
389
|
super
|