File: view_helper.rb

package info (click to toggle)
ruby-secure-headers 6.3.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 496 kB
  • sloc: ruby: 3,342; makefile: 5
file content (171 lines) | stat: -rw-r--r-- 5,998 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# frozen_string_literal: true
module SecureHeaders
  module ViewHelpers
    include SecureHeaders::HashHelper
    SECURE_HEADERS_RAKE_TASK = "rake secure_headers:generate_hashes"

    class UnexpectedHashedScriptException < StandardError; end

    # Public: create a style tag using the content security policy nonce.
    # Instructs secure_headers to append a nonce to style-src directive.
    #
    # Returns an html-safe style tag with the nonce attribute.
    def nonced_style_tag(content_or_options = {}, &block)
      nonced_tag(:style, content_or_options, block)
    end

    # Public: create a stylesheet link tag using the content security policy nonce.
    # Instructs secure_headers to append a nonce to style-src directive.
    #
    # Returns an html-safe link tag with the nonce attribute.
    def nonced_stylesheet_link_tag(*args, &block)
      opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:style))

      stylesheet_link_tag(*args, **opts, &block)
    end

    # Public: create a script tag using the content security policy nonce.
    # Instructs secure_headers to append a nonce to script-src directive.
    #
    # Returns an html-safe script tag with the nonce attribute.
    def nonced_javascript_tag(content_or_options = {}, &block)
      nonced_tag(:script, content_or_options, block)
    end

    # Public: create a script src tag using the content security policy nonce.
    # Instructs secure_headers to append a nonce to script-src directive.
    #
    # Returns an html-safe script tag with the nonce attribute.
    def nonced_javascript_include_tag(*args, &block)
      opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:script))

      javascript_include_tag(*args, **opts, &block)
    end

    # Public: create a script Webpacker pack tag using the content security policy nonce.
    # Instructs secure_headers to append a nonce to script-src directive.
    #
    # Returns an html-safe script tag with the nonce attribute.
    def nonced_javascript_pack_tag(*args, &block)
      opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:script))

      javascript_pack_tag(*args, **opts, &block)
    end

    # Public: create a stylesheet Webpacker link tag using the content security policy nonce.
    # Instructs secure_headers to append a nonce to style-src directive.
    #
    # Returns an html-safe link tag with the nonce attribute.
    def nonced_stylesheet_pack_tag(*args, &block)
      opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:style))

      stylesheet_pack_tag(*args, **opts, &block)
    end

    # Public: use the content security policy nonce for this request directly.
    # Instructs secure_headers to append a nonce to style/script-src directives.
    #
    # Returns a non-html-safe nonce value.
    def _content_security_policy_nonce(type)
      case type
      when :script
        SecureHeaders.content_security_policy_script_nonce(@_request)
      when :style
        SecureHeaders.content_security_policy_style_nonce(@_request)
      end
    end
    alias_method :content_security_policy_nonce, :_content_security_policy_nonce

    def content_security_policy_script_nonce
      _content_security_policy_nonce(:script)
    end

    def content_security_policy_style_nonce
      _content_security_policy_nonce(:style)
    end

    ##
    # Checks to see if the hashed code is expected and adds the hash source
    # value to the current CSP.
    #
    # By default, in development/test/etc. an exception will be raised.
    def hashed_javascript_tag(raise_error_on_unrecognized_hash = nil, &block)
      hashed_tag(
        :script,
        :script_src,
        Configuration.instance_variable_get(:@script_hashes),
        raise_error_on_unrecognized_hash,
        block
      )
    end

    def hashed_style_tag(raise_error_on_unrecognized_hash = nil, &block)
      hashed_tag(
        :style,
        :style_src,
        Configuration.instance_variable_get(:@style_hashes),
        raise_error_on_unrecognized_hash,
        block
      )
    end

    private

    def hashed_tag(type, directive, hashes, raise_error_on_unrecognized_hash, block)
      if raise_error_on_unrecognized_hash.nil?
        raise_error_on_unrecognized_hash = ENV["RAILS_ENV"] != "production"
      end

      content = capture(&block)
      file_path = File.join("app", "views", self.instance_variable_get(:@virtual_path) + ".html.erb")

      if raise_error_on_unrecognized_hash
        hash_value = hash_source(content)
        message = unexpected_hash_error_message(file_path, content, hash_value)

        if hashes.nil? || hashes[file_path].nil? || !hashes[file_path].include?(hash_value)
          raise UnexpectedHashedScriptException.new(message)
        end
      end

      SecureHeaders.append_content_security_policy_directives(request, directive => hashes[file_path])

      content_tag type, content
    end

    def unexpected_hash_error_message(file_path, content, hash_value)
      <<-EOF
\n\n*** WARNING: Unrecognized hash in #{file_path}!!! Value: #{hash_value} ***
#{content}
*** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/secure_headers_generated_hashes.yml:***
#{file_path}:
- \"#{hash_value}\"\n\n
      NOTE: dynamic javascript is not supported using script hash integration
      on purpose. It defeats the point of using it in the first place.
      EOF
    end

    def nonced_tag(type, content_or_options, block)
      options = {}
      content = if block
        options = content_or_options
        capture(&block)
      else
        content_or_options.html_safe # :'(
      end
      content_tag type, content, options.merge(nonce: _content_security_policy_nonce(type))
    end

    def extract_options(args)
      if args.last.is_a? Hash
        args.pop
      else
        {}
      end
    end
  end
end

ActiveSupport.on_load :action_view do
  include SecureHeaders::ViewHelpers
end if defined?(ActiveSupport)