Skip to content

Commit

Permalink
Merge pull request #304 from solenko/2.x
Browse files Browse the repository at this point in the history
Restore Rails 2 support
  • Loading branch information
oreoshake committed Nov 28, 2016
2 parents 87cc0c2 + 07d0ec9 commit 56dc46d
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 152 deletions.
6 changes: 5 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ source 'https://rubygems.org'
gemspec

group :test do
gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
gem 'listen', '<= 3.0.8', :platforms => [:ruby_19, :ruby_20, :ruby_22]
gem "term-ansicolor", "< 1.4"
gem "tins", "< 1.3.4"

gem "guard-rspec", :platforms => [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
gem 'test-unit', '~> 3.0'
gem 'rails', '3.2.22'
gem 'sqlite3', :platforms => [:ruby, :mswin, :mingw]
Expand Down
151 changes: 2 additions & 149 deletions lib/secure_headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "secure_headers/headers/x_content_type_options"
require "secure_headers/headers/x_download_options"
require "secure_headers/headers/x_permitted_cross_domain_policies"
require "secure_headers/controller_extension"
require "secure_headers/railtie"
require "secure_headers/hash_helper"
require "secure_headers/view_helper"
Expand Down Expand Up @@ -62,10 +63,7 @@ def configure &block

class << self
def append_features(base)
base.module_eval do
extend ClassMethods
include InstanceMethods
end
base.send(:include, ControllerExtension)
end

def header_hash(options = nil)
Expand All @@ -92,149 +90,4 @@ def get_a_header(klass, options)
end
end

module ClassMethods
attr_writer :secure_headers_options
def secure_headers_options
if @secure_headers_options
@secure_headers_options
elsif superclass.respond_to?(:secure_headers_options) # stop at application_controller
superclass.secure_headers_options
else
{}
end
end

def ensure_security_headers options = {}
if RUBY_VERSION == "1.8.7"
warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
end
self.secure_headers_options = options
hook = respond_to?(:before_action) ? :before_action : :before_filter
ALL_FILTER_METHODS.each do |method|
send(hook, method)
end
end
end

module InstanceMethods
def set_security_headers(options = self.class.secure_headers_options)
set_csp_header(request, options[:csp])
set_hsts_header(options[:hsts])
set_hpkp_header(options[:hpkp])
set_x_frame_options_header(options[:x_frame_options])
set_x_xss_protection_header(options[:x_xss_protection])
set_x_content_type_options_header(options[:x_content_type_options])
set_x_download_options_header(options[:x_download_options])
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
end

# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
# set_csp_header(+Rack::Request+) - uses the parameter and and SecureHeader::Configuration settings
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
# set_csp_header(+Rack::Request+, +Hash+)
def set_csp_header(req = nil, config=nil)
if req.is_a?(Hash) || req.is_a?(FalseClass)
config = req
end

config = self.class.secure_headers_options[:csp] if config.nil?
config = secure_header_options_for :csp, config

return if config == false

if config && config[:script_hash_middleware]
ContentSecurityPolicy.add_to_env(request, self, config)
else
csp_header = ContentSecurityPolicy.new(config, :request => request, :controller => self)
set_header(csp_header)
end
end


def prep_script_hash
if ::SecureHeaders::Configuration.script_hashes
@script_hashes = ::SecureHeaders::Configuration.script_hashes.dup
ActiveSupport::Notifications.subscribe("render_partial.action_view") do |event_name, start_at, end_at, id, payload|
save_hash_for_later payload
end

ActiveSupport::Notifications.subscribe("render_template.action_view") do |event_name, start_at, end_at, id, payload|
save_hash_for_later payload
end
end
end

def save_hash_for_later payload
matching_hashes = @script_hashes[payload[:identifier].gsub(Rails.root.to_s + "/", "")] || []

if payload[:layout]
# We're assuming an html.erb layout for now. Will need to handle mustache too, just not sure of the best way to do this
layout_hashes = @script_hashes[File.join("app", "views", payload[:layout]) + '.html.erb']

matching_hashes << layout_hashes if layout_hashes
end

if matching_hashes.any?
request.env[HASHES_ENV_KEY] = ((request.env[HASHES_ENV_KEY] || []) << matching_hashes).flatten
end
end

def set_x_frame_options_header(options=self.class.secure_headers_options[:x_frame_options])
set_a_header(:x_frame_options, XFrameOptions, options)
end

def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
set_a_header(:x_content_type_options, XContentTypeOptions, options)
end

def set_x_xss_protection_header(options=self.class.secure_headers_options[:x_xss_protection])
set_a_header(:x_xss_protection, XXssProtection, options)
end

def set_hsts_header(options=self.class.secure_headers_options[:hsts])
return unless request.ssl?
set_a_header(:hsts, StrictTransportSecurity, options)
end

def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
return unless request.ssl?
config = secure_header_options_for :hpkp, options

return if config == false || config.nil?

hpkp_header = PublicKeyPins.new(config)
set_header(hpkp_header)
end

def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
set_a_header(:x_download_options, XDownloadOptions, options)
end

def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
end

private

# we can't use ||= because I'm overloading false => disable, nil => default
# both of which trigger the conditional assignment
def secure_header_options_for(type, options)
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
end

def set_a_header(name, klass, options=nil)
options = secure_header_options_for(name, options)
return if options == false
set_header(SecureHeaders::get_a_header(klass, options))
end

def set_header(name_or_header, value=nil)
if name_or_header.is_a?(Header)
header = name_or_header
response.headers[header.name] = header.value
else
response.headers[name_or_header] = value
end
end
end
end
158 changes: 158 additions & 0 deletions lib/secure_headers/controller_extension.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
module SecureHeaders
module ControllerExtension
class << self
def append_features(base)
base.module_eval do
extend ClassMethods
include InstanceMethods
end
end
end

module ClassMethods
attr_writer :secure_headers_options
def secure_headers_options
if @secure_headers_options
@secure_headers_options
elsif superclass.respond_to?(:secure_headers_options) # stop at application_controller
superclass.secure_headers_options
else
{}
end
end

def ensure_security_headers options = {}
if RUBY_VERSION == "1.8.7"
warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
end
self.secure_headers_options = options
hook = respond_to?(:before_action) ? :before_action : :before_filter
::SecureHeaders::ALL_FILTER_METHODS.each do |method|
send(hook, method)
end
end
end

module InstanceMethods
def set_security_headers(options = self.class.secure_headers_options)
set_csp_header(request, options[:csp])
set_hsts_header(options[:hsts])
set_hpkp_header(options[:hpkp])
set_x_frame_options_header(options[:x_frame_options])
set_x_xss_protection_header(options[:x_xss_protection])
set_x_content_type_options_header(options[:x_content_type_options])
set_x_download_options_header(options[:x_download_options])
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
end

# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
# set_csp_header(+Rack::Request+) - uses the parameter and and SecureHeader::Configuration settings
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
# set_csp_header(+Rack::Request+, +Hash+)
def set_csp_header(req = nil, config=nil)
if req.is_a?(Hash) || req.is_a?(FalseClass)
config = req
end

config = self.class.secure_headers_options[:csp] if config.nil?
config = secure_header_options_for :csp, config

return if config == false

if config && config[:script_hash_middleware]
ContentSecurityPolicy.add_to_env(request, self, config)
else
csp_header = ContentSecurityPolicy.new(config, :request => request, :controller => self)
set_header(csp_header)
end
end


def prep_script_hash
if ::SecureHeaders::Configuration.script_hashes
@script_hashes = ::SecureHeaders::Configuration.script_hashes.dup
ActiveSupport::Notifications.subscribe("render_partial.action_view") do |event_name, start_at, end_at, id, payload|
save_hash_for_later payload
end

ActiveSupport::Notifications.subscribe("render_template.action_view") do |event_name, start_at, end_at, id, payload|
save_hash_for_later payload
end
end
end

def save_hash_for_later payload
matching_hashes = @script_hashes[payload[:identifier].gsub(Rails.root.to_s + "/", "")] || []

if payload[:layout]
# We're assuming an html.erb layout for now. Will need to handle mustache too, just not sure of the best way to do this
layout_hashes = @script_hashes[File.join("app", "views", payload[:layout]) + '.html.erb']

matching_hashes << layout_hashes if layout_hashes
end

if matching_hashes.any?
request.env[HASHES_ENV_KEY] = ((request.env[HASHES_ENV_KEY] || []) << matching_hashes).flatten
end
end

def set_x_frame_options_header(options=self.class.secure_headers_options[:x_frame_options])
set_a_header(:x_frame_options, XFrameOptions, options)
end

def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
set_a_header(:x_content_type_options, XContentTypeOptions, options)
end

def set_x_xss_protection_header(options=self.class.secure_headers_options[:x_xss_protection])
set_a_header(:x_xss_protection, XXssProtection, options)
end

def set_hsts_header(options=self.class.secure_headers_options[:hsts])
return unless request.ssl?
set_a_header(:hsts, StrictTransportSecurity, options)
end

def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
return unless request.ssl?
config = secure_header_options_for :hpkp, options

return if config == false || config.nil?

hpkp_header = PublicKeyPins.new(config)
set_header(hpkp_header)
end

def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
set_a_header(:x_download_options, XDownloadOptions, options)
end

def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
end

private

# we can't use ||= because I'm overloading false => disable, nil => default
# both of which trigger the conditional assignment
def secure_header_options_for(type, options)
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
end

def set_a_header(name, klass, options=nil)
options = secure_header_options_for(name, options)
return if options == false
set_header(SecureHeaders::get_a_header(klass, options))
end

def set_header(name_or_header, value=nil)
if name_or_header.is_a?(Header)
header = name_or_header
response.headers[header.name] = header.value
else
response.headers[name_or_header] = value
end
end
end
end
end
4 changes: 2 additions & 2 deletions lib/secure_headers/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Railtie < Rails::Engine

initializer "secure_headers.action_controller" do
ActiveSupport.on_load(:action_controller) do
include ::SecureHeaders
include ::SecureHeaders::ControllerExtension

unless Rails.application.config.action_dispatch.default_headers.nil?
conflicting_headers.each do |header|
Expand All @@ -26,7 +26,7 @@ class Railtie < Rails::Engine
else
module ActionController
class Base
include ::SecureHeaders
include ::SecureHeaders::ControllerExtension
end
end
end

0 comments on commit 56dc46d

Please sign in to comment.