|
1 | 1 | module SecureHeaders |
2 | 2 | module ViewHelpers |
| 3 | + include SecureHeaders::HashHelper |
| 4 | + SECURE_HEADERS_RAKE_TASK = "rake secure_headers:generate_hashes" |
| 5 | + |
| 6 | + class UnexpectedHashedScriptException < StandardError; end |
| 7 | + |
3 | 8 | # Public: create a style tag using the content security policy nonce. |
4 | 9 | # Instructs secure_headers to append a nonce to style/script-src directives. |
5 | 10 | # |
@@ -29,8 +34,67 @@ def content_security_policy_nonce(type) |
29 | 34 | end |
30 | 35 | end |
31 | 36 |
|
| 37 | + ## |
| 38 | + # Checks to see if the hashed code is expected and adds the hash source |
| 39 | + # value to the current CSP. |
| 40 | + # |
| 41 | + # By default, in development/test/etc. an exception will be raised. |
| 42 | + def hashed_javascript_tag(raise_error_on_unrecognized_hash = nil, &block) |
| 43 | + hashed_tag( |
| 44 | + :script, |
| 45 | + :script_src, |
| 46 | + Configuration.instance_variable_get(:@script_hashes), |
| 47 | + raise_error_on_unrecognized_hash, |
| 48 | + block |
| 49 | + ) |
| 50 | + end |
| 51 | + |
| 52 | + def hashed_style_tag(raise_error_on_unrecognized_hash = nil, &block) |
| 53 | + hashed_tag( |
| 54 | + :style, |
| 55 | + :style_src, |
| 56 | + Configuration.instance_variable_get(:@style_hashes), |
| 57 | + raise_error_on_unrecognized_hash, |
| 58 | + block |
| 59 | + ) |
| 60 | + end |
| 61 | + |
32 | 62 | private |
33 | 63 |
|
| 64 | + def hashed_tag(type, directive, hashes, raise_error_on_unrecognized_hash, block) |
| 65 | + if raise_error_on_unrecognized_hash.nil? |
| 66 | + raise_error_on_unrecognized_hash = ENV["RAILS_ENV"] != "production" |
| 67 | + end |
| 68 | + |
| 69 | + content = capture(&block) |
| 70 | + file_path = File.join('app', 'views', self.instance_variable_get(:@virtual_path) + '.html.erb') |
| 71 | + |
| 72 | + if raise_error_on_unrecognized_hash |
| 73 | + hash_value = hash_source(content) |
| 74 | + message = unexpected_hash_error_message(file_path, content, hash_value) |
| 75 | + |
| 76 | + if hashes.nil? || hashes[file_path].nil? || !hashes[file_path].include?(hash_value) |
| 77 | + raise UnexpectedHashedScriptException.new(message) |
| 78 | + end |
| 79 | + end |
| 80 | + |
| 81 | + SecureHeaders.append_content_security_policy_directives(request, directive => hashes[file_path]) |
| 82 | + |
| 83 | + content_tag type, content |
| 84 | + end |
| 85 | + |
| 86 | + def unexpected_hash_error_message(file_path, content, hash_value) |
| 87 | + <<-EOF |
| 88 | +\n\n*** WARNING: Unrecognized hash in #{file_path}!!! Value: #{hash_value} *** |
| 89 | +#{content} |
| 90 | +*** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/script_hashes.yml:*** |
| 91 | +#{file_path}: |
| 92 | +- #{hash_value}\n\n |
| 93 | + NOTE: dynamic javascript is not supported using script hash integration |
| 94 | + on purpose. It defeats the point of using it in the first place. |
| 95 | + EOF |
| 96 | + end |
| 97 | + |
34 | 98 | def nonced_tag(type, content_or_options, block) |
35 | 99 | options = {} |
36 | 100 | content = if block |
|
0 commit comments