Class: Merb::Controller
- Inherits:
-
Object
- Object
- Merb::Controller
- Defined in:
- lib/merb_threshold/controller/merb_controller.rb
Constant Summary collapse
- THRESHOLD_OPTIONS =
[:limit, :params]
- THRESHOLD_DEFAULTS =
{ :limit => [0,0.seconds], #[Access, PerSecond] :params => nil #[:list, :of, :params, :to, :use, :in, :key] }
- @@_threshold_map =
Use to keep an index of thresholds for looking up information by name
Mash.new
Class Method Summary collapse
-
.register_threshold(threshold_name, opts = {}) ⇒ Array[~Symbol,Hash]
Registers a threshold for tracking.
-
.threshold_actions(*args) ⇒ Object
A succinct wrapper to bulk register thresholds on actions and check access to those thresholds in before filters.
Instance Method Summary collapse
-
#access_history(curr_threshold_key) ⇒ Array[Fixnum]
private
Looks up the users access history.
-
#exceeded_thresholds ⇒ Array[~Symbol]
private
Gets a list of exceeded thresholds.
-
#is_currently_exceeded?(threshold_name = nil) ⇒ Boolean
Is the threshold currenlty exceeded either by this request or a previous one.
-
#permit_access?(threshold_name = nil) ⇒ Boolean
Is access permitted to the threshold protected resource.
-
#threshold_key(threshold_name) ⇒ ~Symbol
get the key representation of the threshold name.
-
#waiting_period ⇒ Hash
Shortcut to session.
-
#will_permit_another?(threshold_name = nil) ⇒ Boolean
Used for determining if a subsequent request would exceed the threshold Good for protecting a post with a form or display captcha/wait before the threshold is exceeded.
Class Method Details
.register_threshold(threshold_name, opts = {}) ⇒ Array[~Symbol,Hash]
Registers a threshold for tracking
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 35 def register_threshold(threshold_name,opts={}) if threshold_name.is_a?(Hash) raise ArgumentError, "Thresolds must be named!" end opts = THRESHOLD_DEFAULTS.merge(opts) opts.each_key do |key| raise(ArgumentError, "You can only specify known threshold options (#{THRESHOLD_OPTIONS.join(', ')}). #{key} is invalid." ) unless THRESHOLD_OPTIONS.include?(key) end #register it @@_threshold_map[threshold_name] = opts end |
.threshold_actions(*args) ⇒ Object
using the class method threshold_actions registers the threshold (no need for a register_threshold statement) and creates a before filter for the given actions where the actual threshold check will take place
A succinct wrapper to bulk register thresholds on actions and check access to those thresholds in before filters. This method will register the threshold and create the before filters.
The threshold names will be “#controlLer_name/#action_name” when actions are given.
If not actions are specified the threshold will be named for the controller.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 131 def threshold_actions(*args) opts = args.last.is_a?(Hash) ? args.pop : {} thresholds_to_register = args #exctract :limit, :params threshold_opts = {} threshold_opts[:limit] = opts.delete(:limit) || [0, 0.seconds] #Always threshold_opts[:params] = opts.delete(:params) halt_with = opts.delete(:halt_with) #get threshold supported before filter options filter_opts = {} filter_opts[:if] = opts.delete(:if) if opts[:if] filter_opts[:unless] = opts.delete(:unless) if opts[:unless] if thresholds_to_register.empty? # Register a controller level threshold self.register_threshold(controller_name,threshold_opts) self.before(nil,filter_opts) do if !permit_access?(controller_name) && halt_with throw(:halt, halt_with) end end else #register a threshold for each action given thresholds_to_register.each do |action_to_register| tmp_threshold_name = "#{controller_name}/#{action_to_register}".to_sym self.register_threshold(tmp_threshold_name,threshold_opts) self.before(nil, filter_opts.merge({:only => [action_to_register]})) do if !permit_access?(tmp_threshold_name) && halt_with throw(:halt,halt_with) end end end end end |
Instance Method Details
#access_history(curr_threshold_key) ⇒ Array[Fixnum]
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
this is a shortcut to the session hash, thus it needs the threshold_key not the threshold_name
Looks up the users access history
344 345 346 347 348 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 344 def access_history(curr_threshold_key) session[:merb_threshold_history] ||= {} session[:merb_threshold_history][curr_threshold_key] ||= [] session[:merb_threshold_history][curr_threshold_key] end |
#exceeded_thresholds ⇒ Array[~Symbol]
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
this is a shortcut to the session hash, thus it needs the threshold_key not the threshold_name
Gets a list of exceeded thresholds
363 364 365 366 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 363 def exceeded_thresholds session[:merb_threshold_exceeded_thresholds] ||= [] session[:merb_threshold_exceeded_thresholds] end |
#is_currently_exceeded?(threshold_name = nil) ⇒ Boolean
See READEME: will_permit_another? vs is_currently_exceeded?
Is the threshold currenlty exceeded either by this request or a previous one
Good for redirecting access during the current request
223 224 225 226 227 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 223 def is_currently_exceeded?(threshold_name=nil) threshold_name ||= action_name curr_threshold_key = threshold_key(threshold_name) exceeded_thresholds.member? curr_threshold_key end |
#permit_access?(threshold_name = nil) ⇒ Boolean
Is access permitted to the threshold protected resource.
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 281 def permit_access?(threshold_name=nil) threshold_name ||= "#{controller_name}/#{action_name}".to_sym curr_threshold_key = threshold_key(threshold_name) opts = @@_threshold_map[threshold_name] if opts.nil? raise Exception, "Threshold (#{threshold_name}) was not registered" end # keep track of thresholds access and if they were relaxed @relaxed_thresholds ||= {} @relaxed_thresholds[curr_threshold_key] = false # may or may not be exceeded, but threshold was not relaxed frequency = if opts[:limit].is_a?(Array) Frequency.new(*opts[:limit]) else opts[:limit].clone end frequency.load! access_history(curr_threshold_key) # Is this request permitted? if frequency.permit? && !is_currently_exceeded?(threshold_name) # if it is also in the exceeded list access_history(curr_threshold_key) << Time.now.to_i @relaxed_thresholds[curr_threshold_key] = true else # if request wasn't permitted and isn't already marked exceeded, mark it exceeded_thresholds << curr_threshold_key unless is_currently_exceeded?(threshold_name) #set the time until the treshold expires waiting_period[curr_threshold_key] = frequency.wait # try to relax threshold via captcha if enabled, then via waiting if Merb::Plugins.config[:merb_threshold][:recaptcha] @relaxed_thresholds[curr_threshold_key] = relax_via_captcha!(curr_threshold_key) end @relaxed_thresholds[curr_threshold_key] ||= relax_via_waiting! curr_threshold_key end #Only keep the last n number of access where n is frequency.occurence access_history(curr_threshold_key).replace frequency.current_events return @relaxed_thresholds[curr_threshold_key] end |
#threshold_key(threshold_name) ⇒ ~Symbol
This is needed to support Params values as a part of the threshold name
get the key representation of the threshold name. Used to store data
in session. This should be used whenever accessing data stored in the session
hash.
260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 260 def threshold_key(threshold_name) curr_threshold_key = threshold_name.to_s # create key to lookup threshold data from users session opts = @@_threshold_map[threshold_name] if opts[:params] opts[:params].each{ |param_key| curr_threshold_key += "/#{params[param_key]}" } end curr_threshold_key.to_sym end |
#waiting_period ⇒ Hash
values stored in here are keyed with #threshold_key waiting_period is not guaranteed to work instead use waiting_period
Shortcut to session
243 244 245 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 243 def waiting_period session[:merb_threshold_waiting_period] ||= {} end |
#will_permit_another?(threshold_name = nil) ⇒ Boolean
See READEME: will_permit_another? vs is_currently_exceeded?
Used for determining if a subsequent request would exceed the threshold
Good for protecting a post with a form or display captcha/wait before
the threshold is exceeded
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/merb_threshold/controller/merb_controller.rb', line 188 def will_permit_another?(threshold_name=nil) threshold_name ||= action_name opts = @@_threshold_map[threshold_name] curr_threshold_key = threshold_key(threshold_name) # if opts[:limit] is not set that means the threshold hasn't been registered yet # so permit access, the threshold will be registered once threshold() is called # which is usually behind the post request this would be submitted to. if opts[:limit] frequency = if opts[:limit].is_a?(Array) Frequency.new(*opts[:limit]) else opts[:limit].clone end frequency.load! access_history(curr_threshold_key) frequency.permit? else true end end |