Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for hash generation #485

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions lib/secure_headers/task_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

module SecureHeaders
module TaskHelper
include SecureHeaders::HashHelper

INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx
INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx
INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx

def generate_inline_script_hashes(filename)
hashes = []

hashes.concat find_inline_content(filename, INLINE_SCRIPT_REGEX, false)
hashes.concat find_inline_content(filename, INLINE_HASH_SCRIPT_HELPER_REGEX, true)

hashes
end

def generate_inline_style_hashes(filename)
hashes = []

hashes.concat find_inline_content(filename, INLINE_STYLE_REGEX, false)
hashes.concat find_inline_content(filename, INLINE_HASH_STYLE_HELPER_REGEX, true)

hashes
end

def dynamic_content?(filename, inline_script)
!!(
(is_mustache?(filename) && inline_script =~ /\{\{.*\}\}/) ||
(is_erb?(filename) && inline_script =~ /<%.*%>/)
)
end

private

def find_inline_content(filename, regex, strip_trailing_whitespace)
hashes = []
file = File.read(filename)
file.scan(regex) do # TODO don't use gsub
inline_script = Regexp.last_match.captures.last
inline_script.gsub!(/(\r?\n)[\t ]+\z/, '\1') if strip_trailing_whitespace
if dynamic_content?(filename, inline_script)
puts "Looks like there's some dynamic content inside of a tag :-/"
puts "That pretty much means the hash value will never match."
puts "Code: " + inline_script
puts "=" * 20
end

hashes << hash_source(inline_script)
end
hashes
end

def is_erb?(filename)
filename =~ /\.erb\Z/
end

def is_mustache?(filename)
filename =~ /\.mustache\Z/
end
end
end
57 changes: 4 additions & 53 deletions lib/tasks/tasks.rake
Original file line number Diff line number Diff line change
@@ -1,58 +1,8 @@
# frozen_string_literal: true
INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX
INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx unless defined? INLINE_STYLE_REGEX
INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_SCRIPT_HELPER_REGEX
INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_STYLE_HELPER_REGEX
require "secure_headers/task_helper"

namespace :secure_headers do
include SecureHeaders::HashHelper

def is_erb?(filename)
filename =~ /\.erb\Z/
end

def is_mustache?(filename)
filename =~ /\.mustache\Z/
end

def dynamic_content?(filename, inline_script)
(is_mustache?(filename) && inline_script =~ /\{\{.*\}\}/) ||
(is_erb?(filename) && inline_script =~ /<%.*%>/)
end

def find_inline_content(filename, regex, hashes, strip_trailing_whitespace)
file = File.read(filename)
file.scan(regex) do # TODO don't use gsub
inline_script = Regexp.last_match.captures.last
inline_script.gsub!(/(\r?\n)[\t ]+\z/, '\1') if strip_trailing_whitespace
if dynamic_content?(filename, inline_script)
puts "Looks like there's some dynamic content inside of a tag :-/"
puts "That pretty much means the hash value will never match."
puts "Code: " + inline_script
puts "=" * 20
end

hashes << hash_source(inline_script)
end
end

def generate_inline_script_hashes(filename)
hashes = []

find_inline_content(filename, INLINE_SCRIPT_REGEX, hashes, false)
find_inline_content(filename, INLINE_HASH_SCRIPT_HELPER_REGEX, hashes, true)

hashes
end

def generate_inline_style_hashes(filename)
hashes = []

find_inline_content(filename, INLINE_STYLE_REGEX, hashes, false)
find_inline_content(filename, INLINE_HASH_STYLE_HELPER_REGEX, hashes, true)

hashes
end
include SecureHeaders::TaskHelper

desc "Generate #{SecureHeaders::Configuration::HASH_CONFIG_FILE}"
task :generate_hashes do |t, args|
Expand All @@ -77,6 +27,7 @@ namespace :secure_headers do
file.write(script_hashes.to_yaml)
end

puts "Script hashes from " + script_hashes.keys.size.to_s + " files added to #{SecureHeaders::Configuration::HASH_CONFIG_FILE}"
file_count = (script_hashes["scripts"].keys + script_hashes["styles"].keys).uniq.count
puts "Script and style hashes from #{file_count} files added to #{SecureHeaders::Configuration::HASH_CONFIG_FILE}"
end
end
123 changes: 123 additions & 0 deletions spec/lib/secure_headers/task_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# frozen_string_literal: true
require "spec_helper"
require "secure_headers/task_helper"

class TestHelper
include SecureHeaders::TaskHelper
end

module SecureHeaders
describe TaskHelper do
subject { TestHelper.new }

let(:template) do
<<EOT
<html>
<head>
<script>alert("Hello World!")</script>
<style>p { color: red; }</style>
<%= hashed_javascript_tag do %>
alert("Using the helper tag!")
<% end %>
<%= hashed_style_tag do %>
p { text-decoration: underline; }
<% end %>
</head>
<body>
<p>Testing</p>
</body>
</html>
EOT
end

let(:template_unindented) do
<<EOT
<html>
<head>
<script>alert("Hello World!")</script>
<style>p { color: red; }</style>
<%= hashed_javascript_tag do %>
alert("Using the helper tag!")
<% end %>
<%= hashed_style_tag do %>
p { text-decoration: underline; }
<% end %>
</head>
<body>
<p>Testing</p>
</body>
</html>
EOT
end

describe "#generate_inline_script_hashes" do
let(:expected_hashes) do
[
"'sha256-EE/znQZ7BcfM3LbsqxUc5JlCtE760Pc2RV18tW90DCo='",
"'sha256-64ro9ciexeO5JqSZcAnhmJL4wbzCrpsZJLWl5H6mrkA='"
]
end

it "returns an array of found script hashes" do
Tempfile.create("script") do |f|
f.write template
f.flush
expect(subject.generate_inline_script_hashes(f.path)).to eq expected_hashes
end
end
it "returns the same array no matter the indentation of helper end tags" do
Tempfile.create("script") do |f|
f.write template_unindented
f.flush
expect(subject.generate_inline_script_hashes(f.path)).to eq expected_hashes
end
end
end

describe "#generate_inline_style_hashes" do
let(:expected_hashes) do
[
"'sha256-pckGv9YvNcB5xy+Y4fbqhyo+ib850wyiuWeNbZvLi00='",
"'sha256-d374zYt40cLTr8J7Cvm/l4oDY4P9UJ8TWhYG0iEglU4='"
]
end

it "returns an array of found style hashes" do
Tempfile.create("style") do |f|
f.write template
f.flush
expect(subject.generate_inline_style_hashes(f.path)).to eq expected_hashes
end
end
it "returns the same array no matter the indentation of helper end tags" do
Tempfile.create("style") do |f|
f.write template_unindented
f.flush
expect(subject.generate_inline_style_hashes(f.path)).to eq expected_hashes
end
end
end

describe "#dynamic_content?" do
context "mustache file" do
it "finds mustache templating tokens" do
expect(subject.dynamic_content?("file.mustache", "var test = {{ dynamic_value }};")).to be true
end

it "returns false when not finding any templating tokens" do
expect(subject.dynamic_content?("file.mustache", "var test = 'static value';")).to be false
end
end

context "erb file" do
it "finds erb templating tokens" do
expect(subject.dynamic_content?("file.erb", "var test = <%= dynamic_value %>;")).to be true
end

it "returns false when not finding any templating tokens" do
expect(subject.dynamic_content?("file.erb", "var test = 'static value';")).to be false
end
end
end
end
end