From f569a14318e25005dfe27a51b3950c426581f18f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 26 Mar 2005 21:41:10 +0000 Subject: Added Verifications that allows you to specify preconditions to actions in form of statements like verify :only => :update_post, :params => "admin_privileges", :redirect_to => { :action => "settings" }, which ensure that the update_post action is only called if admin_privileges is available as a parameter -- otherwise the user is redirected to settings. #897 [Jamis Buck] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1008 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 2 + actionpack/lib/action_controller.rb | 2 + actionpack/lib/action_controller/verification.rb | 79 +++++++++++++ actionpack/test/controller/verification_test.rb | 137 +++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 actionpack/lib/action_controller/verification.rb create mode 100644 actionpack/test/controller/verification_test.rb diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 6c0f1b206a..2aec574270 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Added Verifications that allows you to specify preconditions to actions in form of statements like verify :only => :update_post, :params => "admin_privileges", :redirect_to => { :action => "settings" }, which ensure that the update_post action is only called if admin_privileges is available as a parameter -- otherwise the user is redirected to settings. #897 [Jamis Buck] + * Fixed Form.Serialize for the JavascriptHelper to also seriliaze password fields #934 [dweitzman@gmail.com] * Added JavascriptHelper#escape_javascript as a public method (was private) and made it escape both single and double quotes and new lines #940 [mortonda@dgrmm.net] diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index eccd05702f..517f740f14 100755 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -46,6 +46,7 @@ require 'action_controller/cookies' require 'action_controller/cgi_process' require 'action_controller/caching' require 'action_controller/components' +require 'action_controller/verification' require 'action_view' ActionController::Base.template_class = ActionView::Base @@ -64,4 +65,5 @@ ActionController::Base.class_eval do include ActionController::Session include ActionController::Caching include ActionController::Components + include ActionController::Verification end diff --git a/actionpack/lib/action_controller/verification.rb b/actionpack/lib/action_controller/verification.rb new file mode 100644 index 0000000000..88b2331273 --- /dev/null +++ b/actionpack/lib/action_controller/verification.rb @@ -0,0 +1,79 @@ +module ActionController #:nodoc: + + # This module provides a class-level method for specifying that certain + # actions are guarded against being called without certain prerequisites + # being met. This is essentially a special kind of before_filter. + # + # An action may be guarded against being invoked without certain request + # parameters being set, or without certain session values existing. + # + # When a verification is violated, values may be inserted into the flash, and + # a specified redirection is triggered. + # + # Usage: + # + # class GlobalController < ActionController::Base + # # prevent the #update_settings action from being invoked unless + # # the 'admin_privileges' request parameter exists. + # verify :params => "admin_privileges", :only => :update_post + # :redirect_to => { :action => "settings" } + # + # # disallow a post from being updated if there was no information + # # submitted with the post, and if there is no active post in the + # # session, and if there is no "note" key in the flash. + # verify :params => "post", :session => "post", "flash" => "note", + # :only => :update_post, + # :add_flash => { "alert" => "Failed to create your message" }, + # :redirect_to => :category_url + # + module Verification + def self.append_features(base) #:nodoc: + super + base.extend(ClassMethods) + end + + module ClassMethods + # Verify the given actions so that if certain prerequisites are not met, + # the user is redirected to a different action. The +options+ parameter + # is a hash consisting of the following key/value pairs: + # + # * :params: a single key or an array of keys that must + # be in the @params hash in order for the action(s) to be safely + # called. + # * :session: a single key or an array of keys that must + # be in the @session in order for the action(s) to be safely called. + # * :flash: a single key or an array of keys that must + # be in the flash in order for the action(s) to be safely called. + # * :add_flash: a hash of name/value pairs that should be merged + # into the session's flash if the prerequisites cannot be satisfied. + # * :redirect_to: the redirection parameters to be used when + # redirecting if the prerequisites cannot be satisfied. + # * :only: only apply this verification to the actions specified in + # the associated array (may also be a single value). + # * :except: do not apply this verification to the actions specified in + # the associated array (may also be a single value). + def verify(options={}) + filter_opts = { :only => options[:only], :except => options[:except] } + before_filter(filter_opts) do |c| + c.send :verify_action, options + end + end + end + + def verify_action(options) #:nodoc: + prereqs_invalid = + [*options[:params] ].find { |v| @params[v].nil? } || + [*options[:session]].find { |v| @session[v].nil? } || + [*options[:flash] ].find { |v| flash[v].nil? } + + if prereqs_invalid + flash.update(options[:add_flash]) if options[:add_flash] + redirect_to(options[:redirect_to]) + return false + end + + true + end + private :verify_action + end +end diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb new file mode 100644 index 0000000000..07dc73eb1c --- /dev/null +++ b/actionpack/test/controller/verification_test.rb @@ -0,0 +1,137 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class VerificationTest < Test::Unit::TestCase + class TestController < ActionController::Base + verify :only => :guarded_one, :params => "one", + :redirect_to => { :action => "unguarded" } + + verify :only => :guarded_two, :params => %w( one two ), + :redirect_to => { :action => "unguarded" } + + verify :only => :guarded_with_flash, :params => "one", + :add_flash => { "notice" => "prereqs failed" }, + :redirect_to => { :action => "unguarded" } + + verify :only => :guarded_in_session, :session => "one", + :redirect_to => { :action => "unguarded" } + + verify :only => [:multi_one, :multi_two], :session => %w( one two ), + :redirect_to => { :action => "unguarded" } + + def guarded_one + render_text "#{@params["one"]}" + end + + def guarded_with_flash + render_text "#{@params["one"]}" + end + + def guarded_two + render_text "#{@params["one"]}:#{@params["two"]}" + end + + def guarded_in_session + render_text "#{@session["one"]}" + end + + def multi_one + render_text "#{@session["one"]}:#{@session["two"]}" + end + + def multi_two + render_text "#{@session["two"]}:#{@session["one"]}" + end + + def unguarded + render_text "#{@params["one"]}" + end + end + + def setup + @controller = TestController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_guarded_one_with_prereqs + process "guarded_one", "one" => "here" + assert_equal "here", @response.body + end + + def test_guarded_one_without_prereqs + process "guarded_one" + assert_redirected_to :action => "unguarded" + end + + def test_guarded_with_flash_with_prereqs + process "guarded_with_flash", "one" => "here" + assert_equal "here", @response.body + assert_flash_empty + end + + def test_guarded_with_flash_without_prereqs + process "guarded_with_flash" + assert_redirected_to :action => "unguarded" + assert_flash_equal "prereqs failed", "notice" + end + + def test_guarded_two_with_prereqs + process "guarded_two", "one" => "here", "two" => "there" + assert_equal "here:there", @response.body + end + + def test_guarded_two_without_prereqs_one + process "guarded_two", "two" => "there" + assert_redirected_to :action => "unguarded" + end + + def test_guarded_two_without_prereqs_two + process "guarded_two", "one" => "here" + assert_redirected_to :action => "unguarded" + end + + def test_guarded_two_without_prereqs_both + process "guarded_two" + assert_redirected_to :action => "unguarded" + end + + def test_unguarded_with_params + process "unguarded", "one" => "here" + assert_equal "here", @response.body + end + + def test_unguarded_without_params + process "unguarded" + assert_equal "", @response.body + end + + def test_guarded_in_session_with_prereqs + process "guarded_in_session", {}, "one" => "here" + assert_equal "here", @response.body + end + + def test_guarded_in_session_without_prereqs + process "guarded_in_session" + assert_redirected_to :action => "unguarded" + end + + def test_multi_one_with_prereqs + process "multi_one", {}, "one" => "here", "two" => "there" + assert_equal "here:there", @response.body + end + + def test_multi_one_without_prereqs + process "multi_one" + assert_redirected_to :action => "unguarded" + end + + def test_multi_two_with_prereqs + process "multi_two", {}, "one" => "here", "two" => "there" + assert_equal "there:here", @response.body + end + + def test_multi_two_without_prereqs + process "multi_two" + assert_redirected_to :action => "unguarded" + end +end -- cgit v1.2.3