diff --git a/lib/lotus/action/params.rb b/lib/lotus/action/params.rb index 7d355b0f..c312bd08 100644 --- a/lib/lotus/action/params.rb +++ b/lib/lotus/action/params.rb @@ -26,6 +26,20 @@ class Params # @since 0.1.0 ROUTER_PARAMS = 'router.params'.freeze + # CSRF params key + # + # This key is shared with lotusrb and lotus-helpers + # + # @since x.x.x + # @api private + CSRF_TOKEN = '_csrf_token'.freeze + + # Set of params that are never filtered + # + # @since x.x.x + # @api private + DEFAULT_PARAMS = Hash[CSRF_TOKEN => true].freeze + # Separator for #get # # @since 0.4.0 @@ -201,6 +215,18 @@ def to_h end alias_method :to_hash, :to_h + # Assign CSRF Token. + # This method is here for compatibility with Lotus::Validations. + # + # NOTE: When we will not support indifferent access anymore, we can probably + # remove this method. + # + # @since x.x.x + # @api private + def _csrf_token=(value) + @attributes.set(CSRF_TOKEN, value) + end + private # @since 0.3.1 # @api private @@ -236,12 +262,20 @@ def _params def _whitelisted_params {}.tap do |result| _raw.to_h.each do |k, v| - next unless self.class.defined_attributes.include?(k.to_s) + next unless assign_attribute?(k) result[k] = v end end end + + # Override Lotus::Validations method + # + # @since x.x.x + # @api private + def assign_attribute?(key) + DEFAULT_PARAMS[key.to_s] || super + end end end end diff --git a/lib/lotus/action/rack.rb b/lib/lotus/action/rack.rb index b4d58017..06205745 100644 --- a/lib/lotus/action/rack.rb +++ b/lib/lotus/action/rack.rb @@ -255,7 +255,15 @@ def send_file(path) # # @since 0.3.2 def head? - @_env[REQUEST_METHOD] == HEAD + request_method == HEAD + end + + # NOTE: Lotus::Action::CSRFProtection (lotusrb gem) depends on this. + # + # @api private + # @since x.x.x + def request_method + @_env[REQUEST_METHOD] end end end diff --git a/lib/lotus/action/session.rb b/lib/lotus/action/session.rb index 645dd7c7..b326bc49 100644 --- a/lib/lotus/action/session.rb +++ b/lib/lotus/action/session.rb @@ -20,6 +20,16 @@ module Session # @api private ERRORS_KEY = :__errors + # Add session to default exposures + # + # @since x.x.x + # @api private + def self.included(action) + action.class_eval do + expose :session + end + end + protected # Gets the session from the request and expose it as an Hash. diff --git a/test/action/params_test.rb b/test/action/params_test.rb index 21c1155f..24123006 100644 --- a/test/action/params_test.rb +++ b/test/action/params_test.rb @@ -23,9 +23,11 @@ end it 'raw gets all params' do - @action.call({id: 1, unknown: 2}) - @action.params.raw.get(:id).must_equal 1 - @action.params.raw.get(:unknown).must_equal 2 + @action.call({id: '1', unknown: '2', _csrf_token: '3'}) + + @action.params.raw.get(:id).must_equal '1' + @action.params.raw.get(:unknown).must_equal '2' + @action.params.raw.get(:_csrf_token).must_equal '3' end end @@ -35,9 +37,11 @@ end it 'raw gets all params' do - @action.call({id: 1, unknown: 2}) - @action.params.raw.get(:id).must_equal 1 - @action.params.raw.get(:unknown).must_equal 2 + @action.call({id: '1', unknown: '2', _csrf_token: '3'}) + + @action.params.raw.get(:id).must_equal '1' + @action.params.raw.get(:unknown).must_equal '2' + @action.params.raw.get(:_csrf_token).must_equal '3' end end end @@ -100,6 +104,11 @@ _, _, body = @action.call({id: 23, unknown: 4}) body.must_equal [%({"id"=>23})] end + + it "doesn't filter _csrf_token" do + _, _, body = @action.call(_csrf_token: 'abc') + body.must_equal [%({"_csrf_token"=>"abc"})] + end end describe "in a Rack context" do @@ -107,6 +116,11 @@ response = Rack::MockRequest.new(@action).request('PATCH', "?id=23", params: { x: { foo: 'bar' } }) response.body.must_match %({"id"=>"23"}) end + + it "doesn't filter _csrf_token" do + response = Rack::MockRequest.new(@action).request('PATCH', "?id=23", params: { _csrf_token: 'def', x: { foo: 'bar' } }) + response.body.must_match %("_csrf_token"=>"def") + end end describe "with Lotus::Router" do diff --git a/test/fixtures.rb b/test/fixtures.rb index 3ef7b8b0..e748dcf4 100644 --- a/test/fixtures.rb +++ b/test/fixtures.rb @@ -1207,3 +1207,11 @@ def call(env) end end end + +class MethodInspectionAction + include Lotus::Action + + def call(params) + self.body = request_method + end +end diff --git a/test/rack_test.rb b/test/rack_test.rb new file mode 100644 index 00000000..cebb23fc --- /dev/null +++ b/test/rack_test.rb @@ -0,0 +1,16 @@ +require 'test_helper' + +describe Lotus::Action::Rack do + before do + @action = MethodInspectionAction.new + end + + ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'TRACE', 'OPTIONS'].each do |verb| + it "returns current request method (#{ verb })" do + env = Rack::MockRequest.env_for('/', method: verb) + _, _, body = @action.call(env) + + body.must_equal [verb] + end + end +end diff --git a/test/session_test.rb b/test/session_test.rb index 702a829f..e3de543f 100644 --- a/test/session_test.rb +++ b/test/session_test.rb @@ -6,14 +6,21 @@ action = SessionAction.new action.call({'rack.session' => session = { 'user_id' => '23' }}) - action.send(:session).must_equal(session) + action.__send__(:session).must_equal(session) end it 'returns empty hash when it is missing' do action = SessionAction.new action.call({}) - action.send(:session).must_equal({}) + action.__send__(:session).must_equal({}) + end + + it 'exposes session' do + action = SessionAction.new + action.call({'rack.session' => session = { 'foo' => 'bar' }}) + + action.exposures[:session].must_equal(session) end end end