Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #8891: fix_for_around_filters_returning_false.2.patch

File fix_for_around_filters_returning_false.2.patch, 10.5 kB (added by skaes, 2 years ago)
  • actionpack/lib/action_controller/filters.rb

    old new  
    214214    # == Filter Chain Halting 
    215215    # 
    216216    # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request 
    217     # before controller action is run. This is useful, for example, to deny 
     217    # before a controller action is run. This is useful, for example, to deny 
    218218    # access to unauthenticated users or to redirect from http to https. 
    219219    # Simply return false from the filter or call render or redirect. 
     220    # If fase is returned from a before or around filter, the after filters 
     221    # will not be executed. 
    220222    # 
    221223    # Around filters halt the request unless the action block is called. 
    222224    # Given these filters 
     
    238240    #   .  .  / 
    239241    #   .  #around (code after yield) 
    240242    #   . / 
    241     #   #after (actual filter code is run
     243    #   #after (actual filter code is run, unless around filter returns false
    242244    # 
    243     # If #around returns before yielding, only #after will be run. The #before 
    244     # filter and controller action will not be run.  If #before returns false, 
    245     # the second half of #around and all of #after will still run but the 
    246     # action will not. 
     245    # If #around returns before yielding, #after will still be run. The #before 
     246    # filter and controller action will not be run. If #before returns false, 
     247    # the second half of #around and will still run but #after and the 
     248    # action will not. If #around returns false, #after will not be run. 
    247249    module ClassMethods 
    248250      # The passed <tt>filters</tt> will be appended to the filter_chain and 
    249251      # will execute before the action on this controller is performed. 
     
    439441        def run(controller) 
    440442          # only filters returning false are halted. 
    441443          if false == @filter.call(controller) 
    442             controller.halt_filter_chain(@filter) 
     444            controller.send :halt_filter_chain, @filter, :returned_false 
    443445          end 
    444446        end 
    445447 
     
    528530 
    529531        def find_filter_append_position(filters, filter_type) 
    530532          # appending an after filter puts it at the end of the call chain 
    531           # before and around filters goe before the first after filter in the chain 
     533          # before and around filters go before the first after filter in the chain 
    532534          unless filter_type == :after 
    533535            filter_chain.each_with_index do |f,i| 
    534536              return i if f.after? 
     
    655657          return filter unless filter_responds_to_before_and_after(filter) 
    656658          Proc.new do |controller, action| 
    657659            if filter.before(controller) == false 
    658               controller.send :halt_filter_chain, filter 
     660              controller.send :halt_filter_chain, filter, :returned_false 
    659661            else 
    660662              begin 
    661663                action.call 
     
    675677        end 
    676678      end 
    677679 
    678       def filter_chain 
    679         self.class.filter_chain 
     680      protected 
     681 
     682      def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: 
     683        @before_filter_chain_aborted = false 
     684        process_without_filters(request, response, method, *arguments) 
    680685      end 
    681686 
     687      private 
     688 
     689      def perform_action_with_filters 
     690        call_filters(self.class.filter_chain, 0, 0) 
     691      end 
     692 
     693      def call_filters(chain, index, nesting) 
     694        index = run_before_filters(chain, index, nesting) 
     695        aborted = @before_filter_chain_aborted 
     696        perform_action_without_filters unless performed? || aborted 
     697        return index if nesting != 0 || aborted 
     698        run_after_filters(chain, index) 
     699      end 
     700 
    682701      def skip_excluded_filters(chain, index) 
    683702        while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name) 
    684703          index = index.next 
     
    686705        [filter, index] 
    687706      end 
    688707 
    689       def call_filters(chain, index, nesting) 
    690         # run before filters until we find an after filter or around filter 
     708      def run_before_filters(chain, index, nesting) 
    691709        while chain[index] 
    692710          filter, index = skip_excluded_filters(chain, index) 
    693711          break unless filter # end of call chain reached 
    694712          case filter.type 
    695713          when :before 
    696             # invoke before filter 
    697             filter.run(self) 
     714            filter.run(self)  # invoke before filter 
    698715            index = index.next 
    699716            break if @before_filter_chain_aborted 
    700717          when :around 
     718            yielded = false 
    701719            filter.call(self) do 
     720              yielded = true 
    702721              # all remaining before and around filters will be run in this call 
    703722              index = call_filters(chain, index.next, nesting.next) 
    704723            end 
     724            halt_filter_chain(filter, :did_not_yield) unless yielded 
    705725            break 
    706726          else 
    707             # no before or around filters left 
    708             break 
     727            break  # no before or around filters left 
    709728          end 
    710729        end 
     730        index 
     731      end 
    711732 
    712         aborted = @before_filter_chain_aborted 
    713         perform_action_without_filters unless performed? || aborted 
    714         return index if aborted || nesting != 0 
    715  
    716         # if an around filter catches an exception during rendering and handles it, e.g. 
    717         # by rendering an error page, we might have left over around filters in the filter 
    718         # chain. so skip over these. 
    719         index = index.next while (filter = chain[index]) && filter.type == :around 
    720  
    721         # run after filters, if any 
     733      def run_after_filters(chain, index) 
     734        seen_after_filter = false 
    722735        while chain[index] 
    723736          filter, index = skip_excluded_filters(chain, index) 
    724           break unless filter 
     737          break unless filter # end of call chain reached 
    725738          case filter.type 
    726739          when :after 
    727             filter.run(self) 
    728             index = index.next 
     740            seen_after_filter = true 
     741            filter.run(self)  # invoke after filter 
    729742          else 
    730             raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" 
     743            # implementation error or someone has mucked with the filter chain 
     744            raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter 
    731745          end 
     746          index = index.next 
    732747        end 
    733  
    734748        index.next 
    735749      end 
    736750 
    737       def halt_filter_chain(filter) 
    738         logger.info "Filter chain halted as [#{filter.inspect}] returned false." if logger 
     751      def halt_filter_chain(filter, reason) 
    739752        @before_filter_chain_aborted = true 
     753        logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger 
    740754        false 
    741755      end 
    742  
    743       protected 
    744  
    745         def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: 
    746           @before_filter_chain_aborted = false 
    747           process_without_filters(request, response, method, *arguments) 
    748         end 
    749  
    750       private 
    751         def perform_action_with_filters 
    752           call_filters(self.class.filter_chain, 0, 0) 
    753         end 
    754  
    755756    end 
    756757  end 
    757758end 
  • actionpack/test/controller/filters_test.rb

    old new  
    324324      render :text => 'hello' 
    325325    end 
    326326  end 
    327    
     327 
    328328  class ErrorToRescue < Exception; end 
    329    
     329 
    330330  class RescuingAroundFilterWithBlock 
    331331    def filter(controller) 
    332332      begin 
     
    336336      end 
    337337    end 
    338338  end 
    339    
     339 
    340340  class RescuedController < ActionController::Base 
    341341    around_filter RescuingAroundFilterWithBlock.new 
    342      
     342 
    343343    def show 
    344344      raise ErrorToRescue.new("Something made the bad noise.") 
    345345    end 
    346      
     346 
    347347  private 
    348348    def rescue_action(exception) 
    349349      raise exception 
    350350    end 
    351351  end 
    352352 
     353  class NonYieldingAroundFilterController < ActionController::Base 
     354 
     355    before_filter :filter_one 
     356    around_filter :non_yielding_filter 
     357    before_filter :filter_two 
     358    after_filter :filter_three 
     359 
     360    def index 
     361      render :inline => "index" 
     362    end 
     363 
     364    #make sure the controller complains 
     365    def rescue_action(e); raise e; end 
     366 
     367    private 
     368 
     369      def filter_one 
     370        @filters  ||= [] 
     371        @filters  << "filter_one" 
     372      end 
     373 
     374      def filter_two 
     375        @filters  << "filter_two" 
     376      end 
     377 
     378      def non_yielding_filter 
     379        @filters  << "zomg it didn't yield" 
     380        @filter_return_value 
     381      end 
     382 
     383      def filter_three 
     384        @filters  << "filter_three" 
     385      end 
     386 
     387  end 
     388 
     389  def test_non_yielding_around_filters_not_returning_false_do_not_raise 
     390    controller = NonYieldingAroundFilterController.new 
     391    controller.instance_variable_set "@filter_return_value", true 
     392    assert_nothing_raised do 
     393      test_process(controller, "index") 
     394    end 
     395  end 
     396 
     397  def test_non_yielding_around_filters_returning_false_do_not_raise 
     398    controller = NonYieldingAroundFilterController.new 
     399    controller.instance_variable_set "@filter_return_value", false 
     400    assert_nothing_raised do 
     401      test_process(controller, "index") 
     402    end 
     403  end 
     404 
     405  def test_after_filters_are_not_run_if_around_filter_returns_false 
     406    controller = NonYieldingAroundFilterController.new 
     407    controller.instance_variable_set "@filter_return_value", false 
     408    test_process(controller, "index") 
     409    assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters'] 
     410  end 
     411 
     412  def test_after_filters_are_not_run_if_around_filter_does_not_yield 
     413    controller = NonYieldingAroundFilterController.new 
     414    controller.instance_variable_set "@filter_return_value", true 
     415    test_process(controller, "index") 
     416    assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters'] 
     417  end 
     418 
    353419  def test_empty_filter_chain 
    354420    assert_equal 0, EmptyFilterChainController.filter_chain.size 
    355421    assert test_process(EmptyFilterChainController).template.assigns['action_executed'] 
     
    516582  def test_changing_the_requirements 
    517583    assert_equal nil, test_process(ChangingTheRequirementsController, "go_wild").template.assigns['ran_filter'] 
    518584  end 
    519    
     585 
    520586  def test_a_rescuing_around_filter 
    521587    response = nil 
    522588    assert_nothing_raised do 
    523589      response = test_process(RescuedController) 
    524590    end 
    525      
     591 
    526592    assert response.success? 
    527593    assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body) 
    528594  end