strelka 0.0.1.pre.187 → 0.0.1.pre.193

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data.tar.gz.sig +0 -0
  2. data/ChangeLog +94 -26
  3. data/Manifest.txt +4 -2
  4. data/examples/apps/ws-echo +17 -0
  5. data/lib/strelka/app.rb +26 -24
  6. data/lib/strelka/app/auth.rb +2 -1
  7. data/lib/strelka/app/errors.rb +1 -1
  8. data/lib/strelka/app/filters.rb +1 -1
  9. data/lib/strelka/app/negotiation.rb +1 -1
  10. data/lib/strelka/app/parameters.rb +2 -2
  11. data/lib/strelka/app/restresources.rb +1 -1
  12. data/lib/strelka/app/routing.rb +3 -2
  13. data/lib/strelka/app/sessions.rb +3 -3
  14. data/lib/strelka/app/templating.rb +3 -3
  15. data/lib/strelka/authprovider.rb +2 -10
  16. data/lib/strelka/behavior/plugin.rb +3 -3
  17. data/lib/strelka/httprequest.rb +5 -2
  18. data/lib/strelka/httprequest/session.rb +3 -2
  19. data/lib/strelka/httpresponse/session.rb +8 -9
  20. data/lib/strelka/mixins.rb +15 -0
  21. data/lib/strelka/plugins.rb +257 -0
  22. data/lib/strelka/router/default.rb +27 -2
  23. data/lib/strelka/session.rb +20 -2
  24. data/lib/strelka/session/db.rb +20 -10
  25. data/lib/strelka/session/default.rb +41 -18
  26. data/spec/lib/helpers.rb +1 -1
  27. data/spec/strelka/app/auth_spec.rb +1 -1
  28. data/spec/strelka/app/errors_spec.rb +1 -1
  29. data/spec/strelka/app/filters_spec.rb +1 -1
  30. data/spec/strelka/app/negotiation_spec.rb +1 -1
  31. data/spec/strelka/app/parameters_spec.rb +1 -1
  32. data/spec/strelka/app/restresources_spec.rb +1 -1
  33. data/spec/strelka/app/routing_spec.rb +4 -1
  34. data/spec/strelka/app/sessions_spec.rb +63 -17
  35. data/spec/strelka/app/templating_spec.rb +1 -1
  36. data/spec/strelka/app_spec.rb +13 -5
  37. data/spec/strelka/httprequest/session_spec.rb +44 -23
  38. data/spec/strelka/httprequest_spec.rb +21 -0
  39. data/spec/strelka/httpresponse/session_spec.rb +143 -0
  40. data/spec/strelka/{app/plugins_spec.rb → plugins_spec.rb} +64 -53
  41. data/spec/strelka/router/default_spec.rb +15 -0
  42. data/spec/strelka/router/exclusive_spec.rb +14 -0
  43. data/spec/strelka/session/db_spec.rb +11 -0
  44. data/spec/strelka/session/default_spec.rb +10 -2
  45. metadata +119 -37
  46. metadata.gz.sig +0 -0
  47. data/lib/strelka/app/plugins.rb +0 -284
@@ -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
 
@@ -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
- class << self
29
- # The Sequel dataset connection
30
- attr_reader :db
29
+ ##
30
+ # The Sequel dataset connection
31
+ singleton_attr_reader :db
31
32
 
32
- # The Sequel dataset for the sessions table
33
- attr_reader :dataset
33
+ ##
34
+ # The Sequel dataset for the sessions table
35
+ singleton_attr_reader :dataset
34
36
 
35
- # The configured session cookie parameters
36
- attr_accessor :cookie_options
37
- end
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
- class << self
32
- # Persist sessions in memory
33
- attr_reader :sessions
36
+ ##
37
+ # In-memory session store
38
+ singleton_attr_reader :sessions
34
39
 
35
- # The configured session cookie parameters
36
- attr_accessor :cookie_options
37
- end
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 = nil
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
- return id || SecureRandom.hex
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
@@ -38,7 +38,7 @@ require 'mongrel2/testing'
38
38
  require 'strelka'
39
39
 
40
40
  require 'spec/lib/constants'
41
- # require 'spec/lib/matchers'
41
+
42
42
 
43
43
  ### RSpec helper functions.
44
44
  module Strelka::SpecHelpers
@@ -12,7 +12,7 @@ require 'rspec/mocks'
12
12
  require 'spec/lib/helpers'
13
13
 
14
14
  require 'strelka'
15
- require 'strelka/app/plugins'
15
+ require 'strelka/plugins'
16
16
  require 'strelka/app/auth'
17
17
  require 'strelka/authprovider'
18
18
 
@@ -14,7 +14,7 @@ require 'inversion'
14
14
  require 'spec/lib/helpers'
15
15
 
16
16
  require 'strelka'
17
- require 'strelka/app/plugins'
17
+ require 'strelka/plugins'
18
18
  require 'strelka/app/errors'
19
19
 
20
20
  require 'strelka/behavior/plugin'
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/filters'
18
18
 
19
19
  require 'strelka/behavior/plugin'
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/negotiation'
18
18
  require 'strelka/behavior/plugin'
19
19
 
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/parameters'
18
18
  require 'strelka/behavior/plugin'
19
19
 
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/restresources'
18
18
 
19
19
  require 'strelka/behavior/plugin'
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
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/app/plugins'
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
- it "has an associated session class" do
40
- Strelka::App::Sessions.session_class.should be_a( Class )
41
- Strelka::App::Sessions.session_class.should < Strelka::Session
42
- end
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
- it "is can be configured to use a different session class" do
45
- # First, hook the anonymous class up to the 'testing' name using the PluginFactory API
46
- test_session_class = Class.new( Strelka::Session )
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
- # Now configuring it to use the 'testing' session type should set it to use the
50
- # anonymous class
51
- Strelka::App::Sessions.configure( :session_class => 'testing' )
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
- Strelka::App::Sessions.session_class.should == test_session_class
54
- end
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 handle( req )
76
- req.session[ :test ] = 'session data'
77
- return req.response
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
 
@@ -14,7 +14,7 @@ require 'inversion'
14
14
  require 'spec/lib/helpers'
15
15
 
16
16
  require 'strelka'
17
- require 'strelka/app/plugins'
17
+ require 'strelka/plugins'
18
18
  require 'strelka/app/templating'
19
19
 
20
20
  require 'strelka/behavior/plugin'
@@ -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
- extend Strelka::App::Plugin
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
- extend Strelka::App::Plugin
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
- extend Strelka::App::Plugin
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
- extend Strelka::App::Plugin
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
- extend Strelka::App::Plugin
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