Skip to content

Commit

Permalink
Merge branch 'AlfonsoUceda-security_default_headers'
Browse files Browse the repository at this point in the history
  • Loading branch information
jodosha committed Feb 20, 2015
2 parents 4cbeb03 + 60bf6c2 commit 2ed3b78
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 7 deletions.
2 changes: 1 addition & 1 deletion lib/lotus/action/callable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module Callable
def call(env)
_rescue do
@_env = env
@headers = ::Rack::Utils::HeaderHash.new
@headers = ::Rack::Utils::HeaderHash.new(configuration.default_headers)
@params = self.class.params_class.new(@_env)
super @params
end
Expand Down
20 changes: 20 additions & 0 deletions lib/lotus/action/head.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ module Head
# @api private
HTTP_STATUSES_WITHOUT_BODY = Set.new((100..199).to_a << 204 << 205 << 304).freeze


# Entity headers that by RFC are permitted
#
# @since x.x.x
# @api private
ENTITY_HEADERS = {
'Allow' => true,
'Content-Encoding' => true,
'Content-Language' => true,
'Content-Length' => true,
'Content-Location' => true,
'Content-MD5' => true,
'Content-Range' => true,
'Content-Type' => true,
'Expires' => true,
'Last-Modified' => true,
'extension-header' => true
}.freeze

# Ensures to not send body or headers for HEAD requests and/or for status
# codes that doesn't allow them.
#
Expand All @@ -26,6 +45,7 @@ def finish

if _requires_no_body?
@_body = nil
@headers.reject! { |header,_| !ENTITY_HEADERS.include?(header) }
end
end

Expand Down
30 changes: 30 additions & 0 deletions lib/lotus/controller/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,33 @@ def default_charset(charset = nil)
end
end

# Set default headers for all responses
#
# By default this value is an empty hash.
#
# @since x.x.x
#
# @example Getting the value
# require 'lotus/controller'
#
# Lotus::Controller.configuration.default_headers # => {}
#
# @example Setting the value
# require 'lotus/controller'
#
# Lotus::Controller.configure do
# default_headers({
# 'X-Frame-Options' => 'DENY'
# })
# end
def default_headers(headers = nil)
if headers
@default_headers = headers
else
@default_headers
end
end

# Returns a format for the given mime type
#
# @param mime_type [#to_s,#to_str] A mime type
Expand Down Expand Up @@ -510,6 +537,7 @@ def duplicate
c.formats = formats.dup
c.default_format = default_format
c.default_charset = default_charset
c.default_headers = default_headers
end
end

Expand All @@ -534,6 +562,7 @@ def reset!
@formats = DEFAULT_FORMATS.dup
@default_format = nil
@default_charset = nil
@default_headers = {}
@action_module = ::Lotus::Action
end

Expand Down Expand Up @@ -569,6 +598,7 @@ def load!
attr_writer :modules
attr_writer :default_format
attr_writer :default_charset
attr_writer :default_headers
end
end
end
27 changes: 27 additions & 0 deletions test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,34 @@ def hash
end
end

describe '#default_headers' do
describe "when not previously set" do
it 'returns default value' do
@configuration.default_headers.must_equal({})
end
end

describe "when set" do
let(:headers) { {'X-Frame-Options' => 'DENY'} }

before do
@configuration.default_headers(headers)
end

it 'returns the value' do
@configuration.default_headers.must_equal headers
end
end
end

describe 'duplicate' do
before do
@configuration.reset!
@configuration.prepare { include Kernel }
@configuration.format custom: 'custom/format'
@configuration.default_format :html
@configuration.default_charset 'latin1'
@configuration.default_headers({ 'X-Frame-Options' => 'DENY' })
@config = @configuration.duplicate
end

Expand All @@ -288,6 +309,7 @@ def hash
@config.send(:formats).must_equal @configuration.send(:formats)
@config.default_format.must_equal @configuration.default_format
@config.default_charset.must_equal @configuration.default_charset
@config.default_headers.must_equal @configuration.default_headers
end

it "doesn't affect the original configuration" do
Expand All @@ -298,6 +320,7 @@ def hash
@config.format another: 'another/format'
@config.default_format :json
@config.default_charset 'utf-8'
@config.default_headers({ 'X-Frame-Options' => 'ALLOW ALL' })

@config.handle_exceptions.must_equal false
@config.handled_exceptions.must_equal Hash[ArgumentError => 400]
Expand All @@ -306,6 +329,7 @@ def hash
@config.format_for('another/format').must_equal :another
@config.default_format.must_equal :json
@config.default_charset.must_equal 'utf-8'
@config.default_headers.must_equal ({ 'X-Frame-Options' => 'ALLOW ALL' })

@configuration.handle_exceptions.must_equal true
@configuration.handled_exceptions.must_equal Hash[]
Expand All @@ -314,6 +338,7 @@ def hash
@configuration.format_for('another/format').must_be_nil
@configuration.default_format.must_equal :html
@configuration.default_charset.must_equal 'latin1'
@configuration.default_headers.must_equal ({ 'X-Frame-Options' => 'DENY' })
end
end

Expand All @@ -326,6 +351,7 @@ def hash
@configuration.format another: 'another/format'
@configuration.default_format :another
@configuration.default_charset 'kor-1'
@configuration.default_headers({ 'X-Frame-Options' => 'ALLOW DENY' })

@configuration.reset!
end
Expand All @@ -338,6 +364,7 @@ def hash
@configuration.send(:formats).must_equal(Lotus::Controller::Configuration::DEFAULT_FORMATS)
@configuration.default_format.must_be_nil
@configuration.default_charset.must_be_nil
@configuration.default_headers.must_equal({})
end
end
end
22 changes: 19 additions & 3 deletions test/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,9 @@ module MusicPlayer
configure do
handle_exception ArgumentError => 400
action_module MusicPlayer::Action
default_headers({
"X-Frame-Options" => "DENY"
})

prepare do
include Lotus::Action::Cookies
Expand Down Expand Up @@ -779,6 +782,7 @@ class Index

def call(params)
self.body = 'Muzic!'
headers['X-Frame-Options'] = 'ALLOW FROM https://example.org'
end
end

Expand Down Expand Up @@ -852,18 +856,30 @@ def call(params)
end

module HeadTest
Controller = Lotus::Controller.duplicate(self) do
handle_exceptions false
default_headers({
"X-Frame-Options" => "DENY"
})

prepare do
include Lotus::Action::Glue
include Lotus::Action::Session
end
end

module Home
class Index
include Lotus::Action
include HeadTest::Action

def call(params)
self.body = 'index'
end
end

class Code
include Lotus::Action
include Lotus::Action::Cache
include HeadTest::Action
include HeadTest::Action::Cache

def call(params)
content = 'code'
Expand Down
20 changes: 20 additions & 0 deletions test/integration/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,24 @@
code.must_equal 200
body.must_equal ['Luca']
end

describe 'default headers' do
it "if default headers aren't setted only content-type header is returned" do
code, headers, _ = FullStack::Controllers::Home::Index.new.call({})
code.must_equal 200
headers.must_equal({"Content-Type"=>"application/octet-stream; charset=utf-8"})
end

it "if default headers are setted, default headers are returned" do
code, headers, _ = MusicPlayer::Controllers::Artists::Index.new.call({})
code.must_equal 200
headers.must_equal({"Content-Type" => "application/octet-stream; charset=utf-8", "X-Frame-Options" => "DENY"})
end

it "default headers overrided in action" do
code, headers, _ = MusicPlayer::Controllers::Dashboard::Index.new.call({})
code.must_equal 200
headers.must_equal({"Content-Type" => "application/octet-stream; charset=utf-8", "X-Frame-Options" => "ALLOW FROM https://example.org"})
end
end
end
2 changes: 1 addition & 1 deletion test/integration/full_stack_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def app
head '/head', {}, 'HTTP_ACCEPT' => 'text/html'

last_response.body.must_be_empty
last_response.headers['X-Renderable'].must_equal 'false'
last_response.headers['X-Renderable'].must_be_nil
end

it 'in case of redirect and invalid params, it passes errors in session and then deletes them' do
Expand Down
6 changes: 4 additions & 2 deletions test/integration/head_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,22 @@ def response
last_response
end

it "doesn't send body" do
it "doesn't send body and default headers" do
head '/'

response.status.must_equal(200)
response.body.must_equal ""
response.headers.to_a.wont_include ['X-Frame-Options','DENY']
end

HTTP_TEST_STATUSES_WITHOUT_BODY.each do |code|
describe "with: #{ code }" do
it "doesn't send body" do
it "doesn't send body and default headers" do
get "/code/#{ code }"

response.status.must_equal(code)
response.body.must_equal ""
response.headers.to_a.wont_include ['X-Frame-Options','DENY']
end

it "sends Allow header" do
Expand Down

0 comments on commit 2ed3b78

Please sign in to comment.