Skip to content

coryodaniel/merb_threshold

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

merb_threshold
==============

Merb Threshold provides an easy way to apply control the number of times something is done in a merb
app, this includes dispatching to actions, rendering partials, etc.

Thresholds can be applied to multiple partials per page and can even span controllers and actions.

Also all of the methods are pretty well documented, so it might be pretty helpful to look at the method documentation.

When a threshold is exceeded it can be relaxed either by captcha or waiting, depending on the partial that is rendered.

==== Set up

# Add to init.rb
require 'merb_threshold'

Merb::Plugins.config[:merb_threshold] = {
  :public_key           => nil,  	# Recaptcha Public Key
  :private_key          => nil, 	# Recaptcha Private Key
  :recaptcha            => true,	# Enable Recaptcha

	# Only needed if using Merb::Threshold::Helpers
	# both take :partial as an option to specify an alternate partial
	# wait() 
	# wait(:partial => "shared/other_wait_partial")
	#
  :wait_partial         => 'shared/wait_partial', 			#Path to default partial
  :captcha_partial      => 'shared/recaptcha_partial'		#Path to default partial
}


# Two helpers are included: captcha() and wait()
# 	The helpers aren't needed (see API) below, and are just provided as two examples
# 	of how to use the API to render wait and captch partials
#
class Merb::Controller
	include Merb::Threshold::Helpers
end


==== Public API
	Thresholds are shared across controllers, which makes it possible to do things like
	display a shared login partial in your Home controller while your Authentication controller
	manages the access.
	
	Instance Methods
	* Merb::Controller#permit_access?					
		- is access permitted to the resources 	
		- Takes a threshold name
	* Merb::Controller#will_permit_another? 	
		- will the threshold permit another request.  
		- Takes threshold name
	* Merb::Controller#is_currently_exceeded? 
		- is the threshold currently exceeded (may have been exceeded by a previous request)
		- Take threshold name
	
	Class Methods
	* Merb::Controller.register_threshold 		- registers a threshold, all thresholds must be 'registered'	
	* Merb::Controller.threshold_actions 			- This is a helper method.  It registers the thresholds automatically
			and creates before filters to check permit_access?.  If no actions are listed thresholds, are maintained
			on the controller itself (shared between actions).  Optionally a list of action names can be given and an
			threshold will be created for each one.
			
	If you are not keen of the names generated by
	

==== Thresholding at the Controller / Action level
	
	Action based thresholding allows for controlling access to an action as a whole.  The class level 'threshold' method
	is actually just a wrapper around a before filter that calls the instances level threshold.  Providing the
	wrapper does allow for the threshold to halt the filter chain.  
		
	Example:
    class MyController < Application
			threshold_actions :index, :create, :limit => [5, 30.seconds]
    
      #equivalent to:
      register_threshold :"my_controller/index", :limit => [5, 30.seconds]
      before(nil,{:only => [:index]}) do
      	permit_access? :"my_controller/index"
      end
       
			register_threshold :"my_controller/create", :limit => [5, 30.seconds]
      before(nil,{:only => [:create]}) do
      	permit_access? :"my_controller/create"
      end
    
    #create a controller level threshold
    class MyController < Application
      threshold_actions :limit => [5000, 1.day]
        
      #equivalent to:
      register_threshold :my_controller, :limit => [5000, 1.day]
      before(nil,{}) do
      	permit_access? :my_controller
      end
    
    #create 1 action level threshold with :unless statement and halt
    class MyController < Application
    	threshold_actions :search, :limit => [10, 5.minutes], 
      	:unless => :is_admin?, 
      	:halt_with => "Too many searches"
    
    	#equivalent to:
    	register_threshold :"my_controller/search", :limit => [10, 5.minutes]
    	before(nil,{:only => [:search], :unless => :is_admin?}) do
      	if !permit_access?(:"my_controller/search")
        	throw(:halt, "Too many searches")
      	end
    	end
	
==== Partial / View based thresholding

	Partial / View based thresholding can take advantage of action based thresholds or named thresholds.
	class Whatever < Application
		register_threshold :stuff_to_protect, :limit => 1.per(3.minutes)
	# --- apps/views/whatever/index.html.erb
	<!--
		Cool html stuff
	-->
	<% if permit_access? :stuff_to_protect %>
		<!-- Show your stuff, access wasn't thresholded -->
		<div> Coolness?! </div>
	<% else %>
		<%= wait(:stuff_to_protect) %>
		<!-- your users gets a wait message -->
	<% end %>
	
==== Cross Controller/Action thresholding

	Using named thresholds it is possible to control a threshold across several controllers, actions, and partials.
	
	Thresholds are shared across all controllers that extend from Merb::Controller, so a threshold can be accessed
	by any controller, and more importantly partials can be rendered for thresholds in other controllers
	
	Just like in 'Partial / View based thresholding' theses three methods can be used to control the flow of what
	is rendered.
	
	Methods:
		* permit_access?					- Returns True|False, was the request OVER the threshold
		* will_permit_another? 		- Will the threshold permit another request
		* is_currently_exceeded? 	- Is the current request over the threshold
	
	Helpers (Override at your leisure in Merb::Threshold::Helpers)
		* wait(threshold_name) 		- displays the wait message
		* captcha(threshold_name) - displays the captcha
	

=== Threshold Names vs Threshold Keys

	* A threshold name is used as the identifier so the controller can keep track of registered options
	* A threshold key is how to look up a particular threshold's data in the user's session
	
	Threshold keys should be used whenever accessing any of the data stored in the users' session.

=== Clearing / Destroying sessions
	
	Since all merb_threshold data is stored in the session clearing or destroying the session will remove
	any data stored for that session.  This becomes important if you clear session on logout, because it 
	essentially resets the access history for a user.  To get around this simply copy out the merb_threshold
	data before clearing/destroy and then put it back in the new 'anonymous' session afterwards.  This applies
	to logins if they clear/destroy the anonymous session before presenting the authenticated one.
	
	You could do something like this or whatev.
	def logout
		clear_keys = session.keys - [
    	'merb_threshold_history',
    	'merb_threshold_exceeded_thresholds',
    	'merb_threshold_waiting_period']

  	session.regenerate
  	clear_keys.each { |k| session.delete(k) }
	end


=== will_permit_another? vs is_currently_exceeded?

	*  will_permit_another?
		* Note: takes a threshold_name
		* determines if an immediate subsequent request would exceed the threshold
		* Suggested Use: when one request 'protects' another
			* Example: A GET request that retrieved a form could protect the subsequent POST request
		
	*  is_currently_exceeded?
		* Note: takes a threshold_name
		* determines if the current request is in over the threshold
		* Suggested Use: when throttling the amount of requests a resource gets
			* Example: An API that allows X amount of free accesses before display a 'please pay me' page

==== A case for will_permit_another?

	* A sign up page has a limit of 5 signups per minute.
	* A user signs up 5 accounts in a row (w/ no captcha | wait)
	* On the sixth GET of the form the request's call to captcha() determines another request will exceeded the threshold, so a captcha is presented
	
==== A case for will_permit_another?

	* A user is promised access to a free API 5000 times per day
	* The user's app makes 5000 requests
	* On the 5001 request is_currently_exceeded? could be used to render an 'over the limit' partial


=== Misc. Notes

 * Thresholds can be named whatever you want, so they can be programmatically created.  Also
	the option :params => [:blog_id,:etc] is available that will use param values as part of the key
		
 * merb_threshold currently stores everything in the session (may have support for)
		additional stores in the future.  On that note, it is not recommended to be used
		with cookie base sessions because it could be easy for a user to go over 4k worth
		of data if the site is composed of many controllers, actions, and partials

 * A threshold is EXCEEDED when it goes beyond its limit
 		Given:
			register_threshold :index, :limit => [3,30.seconds]
		The threshold would be considered EXCEEDED on its 4th request.
				
 * Time.now is used for all times since access times are relative
		The frequency class could be a lot more useful if it didn't explicitly use Time.now and you could
		look forward and backward over time.  Fortunately that complexity isn't needed for actions because
		you are accessing them 'now' and the plugin is concerned with 'when' they were last accessed.
		
 * If you dont like the way units are cast using :limit => [1,30.minutes]
		You can override Frequency#cast_units OR specify :limt => [1,30,:minutes]

About

Access thresholding for controllers and actions. Set reasonable limits and stop constantly captchaing your users.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages