/
protection_spec.rb
154 lines (129 loc) · 5.14 KB
/
protection_spec.rb
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
# frozen_string_literal: true
RSpec.describe Rack::Protection do
it_behaves_like 'any rack application'
it 'passes on options' do
mock_app do
# the :track option is used by session_hijacking
use Rack::Protection, track: ['HTTP_FOO'], use: [:session_hijacking], except: [:remote_token]
run proc { |_e| [200, { 'content-type' => 'text/plain' }, ['hi']] }
end
session = { foo: :bar }
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
expect(session[:foo]).to eq(:bar)
get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR'
# won't be empty if the remote_token middleware runs after session_hijacking
# why we run the mock app without remote_token
expect(session).to be_empty
end
it 'passes errors through if :reaction => :report is used' do
mock_app do
use Rack::Protection, reaction: :report
run proc { |e| [200, { 'content-type' => 'text/plain' }, [e['protection.failed'].to_s]] }
end
session = { foo: :bar }
post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com')
expect(last_response).to be_ok
expect(body).to eq('true')
end
describe '#react' do
it 'prevents attacks and warns about it' do
io = StringIO.new
mock_app do
use Rack::Protection, logger: Logger.new(io)
run DummyApp
end
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
expect(io.string).to match(/prevented.*Origin/)
end
it 'reports attacks if reaction is to report' do
io = StringIO.new
mock_app do
use Rack::Protection, reaction: :report, logger: Logger.new(io)
run DummyApp
end
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
expect(io.string).to match(/reported.*Origin/)
expect(io.string).not_to match(/prevented.*Origin/)
end
it 'drops the session and warns if reaction is to drop_session' do
io = StringIO.new
mock_app do
use Rack::Protection, reaction: :drop_session, logger: Logger.new(io)
run DummyApp
end
session = { foo: :bar }
post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com')
expect(io.string).to match(/session dropped by Rack::Protection::HttpOrigin/)
expect(session).not_to have_key(:foo)
end
it 'passes errors to reaction method if specified' do
io = StringIO.new
Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect }
mock_app do
use Rack::Protection, reaction: :special, logger: Logger.new(io)
run DummyApp
end
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
expect(io.string).to match(/HTTP_ORIGIN.*malicious.com/)
expect(io.string).not_to match(/reported|prevented/)
end
end
describe '#html?' do
context 'given an appropriate content-type header' do
subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'text/html' }
it { is_expected.to be_truthy }
end
context 'given an appropriate content-type header of text/xml' do
subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'text/xml' }
it { is_expected.to be_truthy }
end
context 'given an appropriate content-type header of application/xml' do
subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'application/xml' }
it { is_expected.to be_truthy }
end
context 'given an inappropriate content-type header' do
subject { Rack::Protection::Base.new(nil).html? 'content-type' => 'image/gif' }
it { is_expected.to be_falsey }
end
context 'given no content-type header' do
subject { Rack::Protection::Base.new(nil).html?({}) }
it { is_expected.to be_falsey }
end
end
describe '#instrument' do
let(:env) { { 'rack.protection.attack' => 'base' } }
let(:instrumenter) { double('Instrumenter') }
after do
app.instrument(env)
end
context 'with an instrumenter specified' do
let(:app) { Rack::Protection::Base.new(nil, instrumenter: instrumenter) }
it { expect(instrumenter).to receive(:instrument).with('rack.protection', env) }
end
context 'with no instrumenter specified' do
let(:app) { Rack::Protection::Base.new(nil) }
it { expect(instrumenter).not_to receive(:instrument) }
end
end
describe 'new' do
it 'should allow disable session protection' do
mock_app do
use Rack::Protection, without_session: true
run DummyApp
end
session = { foo: :bar }
get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a'
get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b'
expect(session[:foo]).to eq :bar
end
it 'should allow disable CSRF protection' do
mock_app do
use Rack::Protection, without_session: true
run DummyApp
end
post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org')
expect(last_response).to be_ok
end
end
end