strelka 0.0.1.pre177 → 0.0.1.pre184

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/ChangeLog +111 -16
  2. data/Manifest.txt +8 -8
  3. data/Rakefile +3 -3
  4. data/bin/leash +51 -28
  5. data/examples/{auth-demo.rb → apps/auth-demo} +3 -3
  6. data/examples/{auth-demo2.rb → apps/auth-demo2} +0 -0
  7. data/examples/{sessions-demo.rb → apps/sessions-demo} +0 -0
  8. data/examples/config.yml +5 -1
  9. data/examples/{examples.css → static/examples.css} +0 -0
  10. data/examples/{examples.html → static/examples.html} +0 -0
  11. data/examples/{auth-form.tmpl → templates/auth-form.tmpl} +0 -0
  12. data/examples/{auth-success.tmpl → templates/auth-success.tmpl} +0 -0
  13. data/examples/{layout.tmpl → templates/layout.tmpl} +0 -0
  14. data/lib/strelka/app/auth.rb +18 -8
  15. data/lib/strelka/app/errors.rb +3 -2
  16. data/lib/strelka/app/filters.rb +2 -0
  17. data/lib/strelka/app/negotiation.rb +2 -0
  18. data/lib/strelka/app/parameters.rb +18 -140
  19. data/lib/strelka/app/plugins.rb +84 -26
  20. data/lib/strelka/app/restresources.rb +26 -18
  21. data/lib/strelka/app/routing.rb +8 -2
  22. data/lib/strelka/app/sessions.rb +7 -0
  23. data/lib/strelka/app/templating.rb +1 -1
  24. data/lib/strelka/app.rb +25 -1
  25. data/lib/strelka/constants.rb +3 -1
  26. data/lib/strelka/paramvalidator.rb +251 -74
  27. data/lib/strelka/session/default.rb +1 -1
  28. data/spec/strelka/app/auth_spec.rb +37 -0
  29. data/spec/strelka/app/errors_spec.rb +0 -2
  30. data/spec/strelka/app/filters_spec.rb +1 -1
  31. data/spec/strelka/app/parameters_spec.rb +4 -92
  32. data/spec/strelka/app/plugins_spec.rb +64 -2
  33. data/spec/strelka/app/restresources_spec.rb +3 -0
  34. data/spec/strelka/app/routing_spec.rb +5 -5
  35. data/spec/strelka/paramvalidator_spec.rb +294 -385
  36. data.tar.gz.sig +0 -0
  37. metadata +126 -46
  38. metadata.gz.sig +0 -0
@@ -35,13 +35,6 @@ require 'strelka/paramvalidator'
35
35
  #
36
36
  # end # class UserManager
37
37
  #
38
- #
39
- # == To-Do
40
- #
41
- # _We may add support for other ways of passing parameters later,
42
- # e.g., via structured entity bodies like JSON, XML, YAML, etc_.
43
- #
44
- #
45
38
  module Strelka::App::Parameters
46
39
  extend Strelka::App::Plugin
47
40
 
@@ -52,114 +45,32 @@ module Strelka::App::Parameters
52
45
  # Class methods to add to classes with routing.
53
46
  module ClassMethods # :nodoc:
54
47
 
55
- # Pattern for matching route parameters
56
- PARAMETER_PATTERN = %r{/:(?<paramname>[a-z]\w*)}i
57
-
58
- # Param defaults
59
- PARAMETER_DEFAULT_OPTIONS = {
60
- :constraint => //,
61
- :required => false,
62
- :description => nil,
63
- }
64
-
65
- # Pattern to use to strip binding operators from parameter patterns so they
66
- # can be used in the middle of routing Regexps.
67
- PARAMETER_PATTERN_STRIP_RE = Regexp.union( '^', '$', '\\A', '\\z', '\\Z' )
68
-
69
- # Options that are passed as Symbols to .param
70
- FLAGS = [ :required, :untaint ]
71
-
72
-
73
- # Default parameters hash
74
- @parameters = {}
75
- @untaint_all_constraints = false
76
-
77
- # The hash of declared parameters
78
- attr_reader :parameters
79
-
80
- # The flag for untainting constrained parameters that match their constraints
81
- attr_writer :untaint_all_constraints
48
+ ##
49
+ # Default ParamValidator
50
+ @paramvalidator = Strelka::ParamValidator.new
51
+ attr_reader :paramvalidator
82
52
 
83
53
 
54
+ ### :call-seq:
55
+ ### param( name, *flags )
56
+ ### param( name, constraint, *flags )
57
+ ### param( name, description, *flags )
58
+ ### param( name, constraint, description, *flags )
59
+ ###
84
60
  ### Declare a parameter with the specified +name+ that will be validated using the given
85
61
  ### +constraint+. The +constraint+ can be any of the types supported by
86
62
  ### Strelka::ParamValidator.
87
- ### :call-seq:
88
- # param( name, *flags )
89
- # param( name, constraint, *flags )
90
- # param( name, description, *flags )
91
- # param( name, constraint, description, *flags )
92
63
  def param( name, *args )
93
64
  Strelka.log.debug "New param %p" % [ name ]
94
- name = name.to_sym
95
-
96
- # Consume the arguments
97
- constraint = args.shift unless args.first.is_a?( String ) || FLAGS.include?( args.first )
98
- constraint ||= name
99
- description = args.shift if args.first.is_a?( String )
100
- # description ||= name.to_s.capitalize
101
- flags = args
102
-
103
- # Give a regexp constraint a named capture group for the constraint name if it
104
- # doesn't already have one
105
- if constraint.is_a?( Regexp )
106
- constraint = Regexp.compile( "(?<#{name}>" + constraint.to_s + ")" ) unless
107
- constraint.names.include?( name.to_s )
108
- Strelka.log.debug " regex constraint is: %p" % [ constraint ]
109
- end
110
-
111
- # Merge the param into the parameters hash
112
- options = PARAMETER_DEFAULT_OPTIONS.dup
113
- options[ :constraint ] = constraint
114
- options[ :description ] = description
115
- options[ :required ] = true if flags.include?( :required )
116
- options[ :untaint ] = true if flags.include?( :untaint )
117
- Strelka.log.debug " param options are: %p" % [ options ]
118
-
119
- self.parameters[ name ] = options
65
+ self.paramvalidator.add( name, *args )
120
66
  end
121
67
 
122
68
 
123
69
  ### Get/set the untainting flag. If set, all parameters which match their constraints
124
70
  ### will also be untainted.
125
71
  def untaint_all_constraints( newval=nil )
126
- Strelka.log.debug "Untaint all constraints: %p:%p" % [ newval, @untaint_all_constraints ]
127
- @untaint_all_constraints = newval unless newval.nil?
128
- return @untaint_all_constraints
129
- end
130
-
131
-
132
- ### Turn the constraint associated with +name+ into a routing component.
133
- def extract_route_from_constraint( name )
134
- name.slice!( 0, 1 ) if name.start_with?( ':' )
135
- Strelka.log.debug " searching for a param for %p" % [ name ]
136
- param = self.parameters[ name.to_sym ] or
137
- raise ScriptError, "no parameter %p defined" % [ name ]
138
-
139
- # Munge the constraint into a Regexp
140
- constraint = param[ :constraint ]
141
- re = case constraint
142
- when Regexp
143
- constraint
144
- when Array
145
- sub_res = constraint.map( &self.method(:extract_route_from_constraint) )
146
- Regexp.union( sub_res )
147
- when Symbol
148
- re = Strelka::ParamValidator.pattern_for_constraint( constraint ) or
149
- raise ScriptError, "no pattern for %p constraint" % [ constraint ]
150
- /(?<#{name}>#{re})/
151
- else
152
- raise ScriptError,
153
- "can't route on a parameter with a %p constraint %p" % [ constraint.class ]
154
- end
155
-
156
- # Unbind the pattern from beginning or end of line.
157
- # :TODO: This is pretty ugly. Find a better way of modifying the regex.
158
- re_str = re.to_s.
159
- sub( %r{\(\?[\-mix]+:(.*)\)}, '\\1' ).
160
- gsub( PARAMETER_PATTERN_STRIP_RE, '' )
161
-
162
- return Regexp.new( re_str, re.options )
72
+ self.paramvalidator.untaint_all = newval unless newval.nil?
73
+ return self.paramvalidator.untaint_all?
163
74
  end
164
75
 
165
76
 
@@ -167,7 +78,7 @@ module Strelka::App::Parameters
167
78
  ### declarations, too.
168
79
  def inherited( subclass )
169
80
  super
170
- subclass.instance_variable_set( :@parameters, self.parameters.dup )
81
+ subclass.instance_variable_set( :@paramvalidator, self.paramvalidator.dup )
171
82
  end
172
83
 
173
84
  end # module ClassMethods
@@ -176,48 +87,15 @@ module Strelka::App::Parameters
176
87
 
177
88
  ### Add a ParamValidator to the given +request+ before passing it on.
178
89
  def handle_request( request, &block )
179
- profile = self.make_validator_profile( request )
180
- self.log.debug "Applying validator profile: %p" % [ profile ]
181
- validator = Strelka::ParamValidator.new( profile, request.params )
182
- self.log.debug " validator: %p" % [ validator ]
90
+ self.log.debug "[:parameters] Wrapping request with parameter validation."
183
91
 
92
+ validator = self.class.paramvalidator.dup
93
+ validator.validate( request.params )
184
94
  request.params = validator
185
- super
186
- end
187
-
188
-
189
-
190
- ### Make a validator profile for Strelka::ParamValidator for the specified
191
- ### +request+ using the declared parameters in the App, returning it as a Hash.
192
- def make_validator_profile( request )
193
- profile = {
194
- :required => [],
195
- :optional => [],
196
- :descriptions => {},
197
- :constraints => {},
198
- :untaint_constraint_fields => [],
199
- :untaint_all_constraints => self.class.untaint_all_constraints,
200
- }
201
95
 
202
- self.log.debug "Validator profile is: %p" % [ profile ]
203
-
204
- return self.class.parameters.inject( profile ) do |accum, (name, opts)|
205
- self.log.debug " adding parameter: %p: %p" % [ name, opts ]
206
- if opts[:required]
207
- accum[:required] << name
208
- else
209
- accum[:optional] << name
210
- end
211
-
212
- accum[:untaint_constraint_fields] << name if opts[:untaint]
213
- accum[:descriptions][ name ] = opts[:description] if opts[:description]
214
- accum[:constraints][ name ] = opts[:constraint]
215
-
216
- accum
217
- end
96
+ super
218
97
  end
219
98
 
220
-
221
99
  end # module Strelka::App::Parameters
222
100
 
223
101
 
@@ -118,16 +118,59 @@ class Strelka::App
118
118
  end
119
119
 
120
120
 
121
- ### Extension callback -- add instance variables to extending objects.
122
- def self::extended( object )
123
- super
124
- object.instance_variable_set( :@plugins, {} )
125
- end
126
-
127
-
128
121
  ### Class methods to add to classes with plugins.
129
122
  module ClassMethods
130
123
 
124
+ ##
125
+ # If plugins have already been installed, this will be the call frame
126
+ # they were first installed from. This is used to warn about installing
127
+ # plugins twice.
128
+ attr_accessor :plugins_installed_from
129
+
130
+
131
+ ### Returns +true+ if the plugins for the extended app class have already
132
+ ### been installed.
133
+ def plugins_installed?
134
+ return !self.plugins_installed_from.nil?
135
+ end
136
+
137
+
138
+ ### Extension callback -- add instance variables to extending objects.
139
+ def inherited( subclass )
140
+ super
141
+ @plugins ||= []
142
+ subclass.instance_variable_set( :@plugins, @plugins.dup )
143
+ subclass.instance_variable_set( :@plugins_installed_from, nil )
144
+ end
145
+
146
+
147
+ ### Load the plugins with the given +names+ and install them.
148
+ def plugins( *names )
149
+ Strelka.log.info "Adding plugins: %s" % [ names.flatten.map(&:to_s).join(', ') ]
150
+
151
+ # Load the associated Plugin Module objects
152
+ names.flatten.each {|name| self.load_plugin(name) }
153
+
154
+ # Add the name/s to the list of mixins to apply on startup
155
+ @plugins |= names
156
+
157
+ # Install the declarative half of the plugin immediately
158
+ names.each do |name|
159
+ plugin = nil
160
+
161
+ if name.is_a?( Module )
162
+ plugin = name
163
+ else
164
+ plugin = Strelka::App.loaded_plugins[ name ]
165
+ end
166
+
167
+ Strelka.log.debug " registering %p" % [ name ]
168
+ self.register_plugin( plugin )
169
+ end
170
+ end
171
+ alias_method :plugin, :plugins
172
+
173
+
131
174
  ### Load the plugin with the given +name+
132
175
  def load_plugin( name )
133
176
 
@@ -146,11 +189,10 @@ class Strelka::App
146
189
  end
147
190
 
148
191
 
149
- ### Install the plugin +mod+ in the receiving class.
150
- def install_plugin( mod )
151
- Strelka.log.debug " adding %p to %p" % [ mod, self ]
152
- include( mod )
153
-
192
+ ### Register the plugin +mod+ in the receiving class. This adds any
193
+ ### declaratives and class-level data necessary for configuring the
194
+ ### plugin.
195
+ def register_plugin( mod )
154
196
  if mod.const_defined?( :ClassMethods )
155
197
  cm_mod = mod.const_get(:ClassMethods)
156
198
  Strelka.log.debug " adding class methods from %p" % [ cm_mod ]
@@ -172,27 +214,43 @@ class Strelka::App
172
214
  end
173
215
 
174
216
 
175
- ### Load the plugins with the given +names+ and install them.
176
- def plugins( *names )
177
- # Load the associated Plugin Module objects
178
- names.flatten.each {|name| self.load_plugin(name) }
217
+ ### Install the mixin part of plugins immediately before the first instance
218
+ ### is created.
219
+ def new( * )
220
+ self.install_plugins unless self.plugins_installed?
221
+ super
222
+ end
223
+
224
+
225
+ ### Install the mixin part of the plugin, in the order determined by
226
+ ### the plugin registry based on the run_before and run_after specifications
227
+ ### of the plugins themselves.
228
+ def install_plugins
229
+ if self.plugins_installed?
230
+ Strelka.log.warn "Plugins were already installed for %p from %p" %
231
+ [ self, self.plugins_installed_from ]
232
+ Strelka.log.info "I'll attempt to install any new ones, but plugin ordering"
233
+ Strelka.log.info "and other functionality might exhibit strange behavior."
234
+ else
235
+ Strelka.log.info "Installing plugins for %p." % [ self ]
236
+ end
179
237
 
180
238
  sorted_plugins = Strelka::App.loaded_plugins.tsort.reverse
181
- Strelka.log.debug "Sorted plugins: app -> %p <- Mongrel2" % [ sorted_plugins ]
182
239
 
183
- # Install the plugins in reverse-sorted order
184
240
  sorted_plugins.each do |name|
185
- plugin = Strelka::App.loaded_plugins[ name ]
186
- Strelka.log.debug "Considering %p" % [ name ]
187
- if names.include?( name ) || names.include?( plugin )
188
- Strelka.log.debug " installing"
189
- self.install_plugin( plugin )
190
- else
191
- Strelka.log.debug " not used by this app; skipping"
241
+ mod = Strelka::App.loaded_plugins[ name ]
242
+
243
+ unless @plugins.include?( name ) || @plugins.include?( mod )
244
+ Strelka.log.debug " skipping %s" % [ name ]
245
+ next
192
246
  end
247
+
248
+ Strelka.log.info " including %p." % [ mod ]
249
+ include( mod )
193
250
  end
251
+
252
+ self.plugins_installed_from = caller( 1 ).first
194
253
  end
195
- alias_method :plugin, :plugins
196
254
 
197
255
  end # module ClassMethods
198
256
 
@@ -62,23 +62,6 @@ module Strelka::App::RestResources
62
62
  }.freeze
63
63
 
64
64
 
65
- ### Inclusion callback -- overridden to also install dependencies.
66
- def self::included( mod )
67
- super
68
-
69
- # Load the plugins this one depends on if they aren't already
70
- mod.plugins :routing, :negotiation, :parameters
71
-
72
- # Add validations for the limit and offset parameters
73
- mod.param :limit, :integer
74
- mod.param :offset, :integer
75
-
76
- # Use the 'exclusive' router instead of the more-flexible
77
- # Mongrel2-style default one
78
- mod.router :exclusive
79
- end
80
-
81
-
82
65
  # Class methods to add to classes with REST resources.
83
66
  module ClassMethods # :nodoc:
84
67
  include Sequel::Inflections
@@ -96,6 +79,23 @@ module Strelka::App::RestResources
96
79
  attr_reader :global_options
97
80
 
98
81
 
82
+ ### Extension callback -- overridden to also install dependencies.
83
+ def self::extended( obj )
84
+ super
85
+
86
+ # Load the plugins this one depends on if they aren't already
87
+ obj.plugins :routing, :negotiation, :parameters
88
+
89
+ # Add validations for the limit and offset parameters
90
+ obj.param :limit, :integer
91
+ obj.param :offset, :integer
92
+
93
+ # Use the 'exclusive' router instead of the more-flexible
94
+ # Mongrel2-style default one
95
+ obj.router :exclusive
96
+ end
97
+
98
+
99
99
  ### Set the prefix for all following resource routes to +route+.
100
100
  def resource_prefix( route )
101
101
  self.global_options[ :prefix ] = route
@@ -141,7 +141,7 @@ module Strelka::App::RestResources
141
141
  ### Add parameter declarations for parameters related to +rsrcobj+.
142
142
  def add_parameters( rsrcobj, options )
143
143
  Strelka.log.debug "Declaring validations for columns from %p" % [ rsrcobj ]
144
- self.untaint_all_constraints = true
144
+ self.untaint_all_constraints
145
145
  rsrcobj.db_schema.each do |col, config|
146
146
  Strelka.log.debug " %s (%p)" % [ col, config[:type] ]
147
147
  param col, config[:type]
@@ -155,9 +155,11 @@ module Strelka::App::RestResources
155
155
  # :TODO: Documentation for HTML mode (possibly using http://swagger.wordnik.com/)
156
156
  Strelka.log.debug "Adding OPTIONS handler for %p" % [ route, rsrcobj ]
157
157
  self.add_route( :OPTIONS, route, options ) do |req|
158
+ self.log.debug "OPTIONS handler!"
158
159
  verbs = self.class.resource_verbs[ route ].sort
159
160
  res = req.response
160
161
 
162
+ self.log.debug " making a reply with Allowed: %s" % [ verbs.join(', ') ]
161
163
  res.header.allowed = verbs.join(', ')
162
164
  res.content_type = 'text/plain'
163
165
  res.body = ''
@@ -498,4 +500,10 @@ module Strelka::App::RestResources
498
500
  end # module ClassMethods
499
501
 
500
502
 
503
+ ### This is just here for logging.
504
+ def handle_request( * ) # :nodoc:
505
+ self.log.debug "[:restresources] handling request for REST resource."
506
+ super
507
+ end
508
+
501
509
  end # module Strelka::App::RestResources
@@ -216,10 +216,12 @@ module Strelka::App::Routing
216
216
  return pattern.split( '/' ).collect do |component|
217
217
 
218
218
  if component.start_with?( ':' )
219
+ Strelka.log.debug "translating parameter component %p to a regexp" % [component]
219
220
  raise ScriptError,
220
221
  "parameter-based routing not supported without a 'parameters' plugin" unless
221
- self.respond_to?( :extract_route_from_constraint )
222
- self.extract_route_from_constraint( component )
222
+ self.respond_to?( :paramvalidator )
223
+ component = component.slice( 1..-1 )
224
+ self.paramvalidator.constraint_regexp_for( component )
223
225
  else
224
226
  component
225
227
  end
@@ -249,6 +251,8 @@ module Strelka::App::Routing
249
251
 
250
252
  ### Dispatch the request using the Router.
251
253
  def handle_request( request, &block )
254
+ self.log.debug "[:routing] Routing request using %p" % [ self.router.class ]
255
+
252
256
  if route = self.router.route_request( request )
253
257
  # Track which route was chosen for later plugins
254
258
  request.notes[:routing][:route] = route
@@ -257,6 +261,8 @@ module Strelka::App::Routing
257
261
  else
258
262
  finish_with HTTP::NOT_FOUND, "The requested resource was not found on this server."
259
263
  end
264
+
265
+ self.log.debug "[:routing] Done with routing."
260
266
  end
261
267
 
262
268
  end # module Strelka::App::Routing
@@ -172,6 +172,13 @@ module Strelka::App::Sessions
172
172
  return super
173
173
  end
174
174
 
175
+
176
+ ### This is just here for logging.
177
+ def handle_request( * ) # :nodoc:
178
+ self.log.debug "[:sessions] Adding sessions to the transaction."
179
+ super
180
+ end
181
+
175
182
  end # module Strelka::App::Sessions
176
183
 
177
184
 
@@ -90,7 +90,7 @@ module Strelka::App::Templating
90
90
  include Strelka::Constants
91
91
  extend Strelka::App::Plugin
92
92
 
93
- run_before :routing, :negotiation
93
+ run_before :routing, :negotiation, :errors
94
94
  run_after :filters
95
95
 
96
96
 
data/lib/strelka/app.rb CHANGED
@@ -53,8 +53,8 @@ class Strelka::App < Mongrel2::Handler
53
53
  Strelka.logger.level = Logger::DEBUG if $VERBOSE
54
54
  Strelka.logger.formatter = Strelka::Logging::ColorFormatter.new( Strelka.logger ) if $stderr.tty?
55
55
 
56
+ Strelka.log.info "Starting up with appid %p." % [ appid ]
56
57
  super( appid )
57
-
58
58
  end
59
59
 
60
60
 
@@ -172,6 +172,13 @@ class Strelka::App < Mongrel2::Handler
172
172
  ### I N S T A N C E M E T H O D S
173
173
  #################################################################
174
174
 
175
+ ### Dump the application stack when a new instance is created.
176
+ def initialize( * )
177
+ self.dump_application_stack
178
+ super
179
+ end
180
+
181
+
175
182
  ######
176
183
  public
177
184
  ######
@@ -192,11 +199,15 @@ class Strelka::App < Mongrel2::Handler
192
199
 
193
200
  # Dispatch the request after allowing plugins to to their thing
194
201
  status_info = catch( :finish ) do
202
+ self.log.debug "Starting dispatch of request %p" % [ request ]
195
203
 
196
204
  # Run fixup hooks on the request
197
205
  request = self.fixup_request( request )
206
+ self.log.debug " done with request fixup"
198
207
  response = self.handle_request( request )
208
+ self.log.debug " done with handler"
199
209
  response = self.fixup_response( response )
210
+ self.log.debug " done with response fixup"
200
211
 
201
212
  nil # rvalue for the catch
202
213
  end
@@ -337,5 +348,18 @@ class Strelka::App < Mongrel2::Handler
337
348
  return response
338
349
  end
339
350
 
351
+
352
+ ### Output the application stack into the logfile.
353
+ def dump_application_stack
354
+ stack = self.class.ancestors.
355
+ reverse.
356
+ drop_while {|mod| mod != Strelka::App }.
357
+ select {|mod| mod.respond_to?(:plugin_name) }.
358
+ reverse.
359
+ collect {|mod| mod.plugin_name }
360
+
361
+ self.log.info "Application stack: request -> %s" % [ stack.join(" -> ") ]
362
+ end
363
+
340
364
  end # class Strelka::App
341
365
 
@@ -12,7 +12,9 @@ module Strelka::Constants
12
12
  include Mongrel2::Constants
13
13
 
14
14
  # The data directory in the project if that exists, otherwise the gem datadir
15
- DATADIR = if File.directory?( 'data/strelka' )
15
+ DATADIR = if ENV['STRELKA_DATADIR']
16
+ Pathname( ENV['STRELKA_DATADIR'] )
17
+ elsif File.directory?( 'data/strelka' )
16
18
  Pathname( 'data/strelka' )
17
19
  elsif path = Gem.datadir('strelka')
18
20
  Pathname( path )