File: railtie_spec.rb

package info (click to toggle)
ruby-secure-headers 7.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 556 kB
  • sloc: ruby: 4,196; makefile: 5
file content (173 lines) | stat: -rw-r--r-- 5,245 bytes parent folder | download
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
172
173
# frozen_string_literal: true
require "spec_helper"

describe "SecureHeaders::Railtie" do
  # Store initializer blocks so we can execute them in tests
  let(:initializer_blocks) { {} }
  let(:default_headers) { {} }

  before do
    # Clean up any previous Rails/ActiveSupport definitions
    Object.send(:remove_const, :Rails) if defined?(Rails)
    Object.send(:remove_const, :ActiveSupport) if defined?(ActiveSupport)

    # Remove the SecureHeaders::Railtie if it was previously defined
    SecureHeaders.send(:remove_const, :Railtie) if defined?(SecureHeaders::Railtie)

    blocks = initializer_blocks
    headers = default_headers

    # Mock ActiveSupport.on_load to immediately execute the block
    stub_const("ActiveSupport", Module.new do
      define_singleton_method(:on_load) do |name, &block|
        block.call if block
      end
    end)

    # Create mock Rails module with application config
    rails_module = Module.new do
      # Create a mock Railtie base class
      const_set(:Railtie, Class.new do
        define_singleton_method(:isolate_namespace) { |*| }
        define_singleton_method(:initializer) do |name, &block|
          blocks[name] = block
        end
        define_singleton_method(:rake_tasks) { |&block| }
      end)

      # Create mock application with config
      config_action_dispatch = Struct.new(:default_headers).new(headers)
      config_middleware = Object.new.tap do |mw|
        mw.define_singleton_method(:insert_before) { |*| }
      end
      config = Struct.new(:action_dispatch, :middleware).new(config_action_dispatch, config_middleware)
      application = Struct.new(:config).new(config)

      define_singleton_method(:application) { application }
    end

    stub_const("Rails", rails_module)
  end

  def load_railtie
    # Load the railtie file fresh
    begin
      load File.expand_path("../../../../lib/secure_headers/railtie.rb", __FILE__)
    rescue LoadError
      spec = Gem::Specification.find_by_name("secure_headers")
      railtie_path = File.join(spec.gem_dir, "lib", "secure_headers", "railtie.rb")
      if File.exist?(railtie_path)
        load railtie_path
      else
        raise LoadError, "secure_headers railtie not found at #{railtie_path}"
      end
    end
  end

  def run_action_controller_initializer
    initializer_blocks["secure_headers.action_controller"]&.call
  end

  describe "case-insensitive header removal" do
    context "with Rails default headers (capitalized format like 'X-Frame-Options')" do
      let(:default_headers) do
        {
          "X-Frame-Options" => "SAMEORIGIN",
          "X-XSS-Protection" => "0",
          "X-Content-Type-Options" => "nosniff",
          "X-Permitted-Cross-Domain-Policies" => "none",
          "X-Download-Options" => "noopen",
          "Referrer-Policy" => "strict-origin-when-cross-origin"
        }
      end

      it "removes capitalized conflicting headers from Rails defaults" do
        load_railtie
        run_action_controller_initializer

        expect(default_headers).to be_empty
      end
    end

    context "with lowercase headers (Rack 3+ format)" do
      let(:default_headers) do
        {
          "x-frame-options" => "SAMEORIGIN",
          "x-xss-protection" => "0",
          "x-content-type-options" => "nosniff"
        }
      end

      it "removes lowercase conflicting headers from Rails defaults" do
        load_railtie
        run_action_controller_initializer

        expect(default_headers).to be_empty
      end
    end

    context "with mixed-case headers" do
      let(:default_headers) do
        {
          "X-FRAME-OPTIONS" => "SAMEORIGIN",
          "x-Xss-Protection" => "0",
          "X-Content-Type-OPTIONS" => "nosniff"
        }
      end

      it "removes mixed-case conflicting headers from Rails defaults" do
        load_railtie
        run_action_controller_initializer

        expect(default_headers).to be_empty
      end
    end

    context "preserving non-conflicting headers" do
      let(:default_headers) do
        {
          "X-Frame-Options" => "SAMEORIGIN",
          "X-Custom-Header" => "custom-value",
          "My-Application-Header" => "app-value"
        }
      end

      it "removes only conflicting headers and preserves custom headers" do
        load_railtie
        run_action_controller_initializer

        expect(default_headers).to eq({
          "X-Custom-Header" => "custom-value",
          "My-Application-Header" => "app-value"
        })
      end
    end

    context "with nil default_headers" do
      let(:default_headers) { nil }

      it "handles nil default_headers gracefully" do
        load_railtie

        expect { run_action_controller_initializer }.not_to raise_error
      end
    end

    context "CSP and HSTS headers" do
      let(:default_headers) do
        {
          "Content-Security-Policy" => "default-src 'self'",
          "Content-Security-Policy-Report-Only" => "default-src 'self'",
          "Strict-Transport-Security" => "max-age=31536000"
        }
      end

      it "removes CSP and HSTS headers regardless of case" do
        load_railtie
        run_action_controller_initializer

        expect(default_headers).to be_empty
      end
    end
  end
end