From 74a01f9bea34c330f434abb36a79d47885690f09 Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Thu, 2 Jun 2022 12:31:52 -0400 Subject: [PATCH 1/2] Refactor rake task methods into module for better testing --- lib/secure_headers/task_helper.rb | 65 +++++++++++ lib/tasks/tasks.rake | 54 +-------- spec/lib/secure_headers/task_helper_spec.rb | 123 ++++++++++++++++++++ 3 files changed, 190 insertions(+), 52 deletions(-) create mode 100644 lib/secure_headers/task_helper.rb create mode 100644 spec/lib/secure_headers/task_helper_spec.rb diff --git a/lib/secure_headers/task_helper.rb b/lib/secure_headers/task_helper.rb new file mode 100644 index 00000000..fa4ba971 --- /dev/null +++ b/lib/secure_headers/task_helper.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module SecureHeaders + module TaskHelper + include SecureHeaders::HashHelper + + INLINE_SCRIPT_REGEX = /()(.*?)<\/script>/mx + INLINE_STYLE_REGEX = /(]*>)(.*?)<\/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 diff --git a/lib/tasks/tasks.rake b/lib/tasks/tasks.rake index cb078246..0d6fb4af 100644 --- a/lib/tasks/tasks.rake +++ b/lib/tasks/tasks.rake @@ -1,58 +1,8 @@ # frozen_string_literal: true -INLINE_SCRIPT_REGEX = /()(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX -INLINE_STYLE_REGEX = /(]*>)(.*?)<\/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| diff --git a/spec/lib/secure_headers/task_helper_spec.rb b/spec/lib/secure_headers/task_helper_spec.rb new file mode 100644 index 00000000..8be633f5 --- /dev/null +++ b/spec/lib/secure_headers/task_helper_spec.rb @@ -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 + < + + + + <%= hashed_javascript_tag do %> + alert("Using the helper tag!") + <% end %> + <%= hashed_style_tag do %> + p { text-decoration: underline; } + <% end %> + + +

Testing

+ + +EOT + end + + let(:template_unindented) do + < + + + + <%= hashed_javascript_tag do %> + alert("Using the helper tag!") +<% end %> + <%= hashed_style_tag do %> + p { text-decoration: underline; } +<% end %> + + +

Testing

+ + +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 From 32d3eb0ba180607b247d3dac43a0a15dd675301e Mon Sep 17 00:00:00 2001 From: Ryan Ahearn Date: Thu, 2 Jun 2022 12:38:17 -0400 Subject: [PATCH 2/2] Fix rake task file count output message --- lib/tasks/tasks.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tasks/tasks.rake b/lib/tasks/tasks.rake index 0d6fb4af..09237bb4 100644 --- a/lib/tasks/tasks.rake +++ b/lib/tasks/tasks.rake @@ -27,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