rack_csrf 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest +1 -0
- data/README.rdoc +18 -3
- data/Rakefile +1 -1
- data/cucumber.yml +1 -1
- data/example/config-with-raise.ru +3 -0
- data/example/config.ru +3 -0
- data/features/browser_only.feature +24 -0
- data/features/empty_responses.feature +5 -5
- data/features/raising_exception.feature +4 -4
- data/features/setup.feature +10 -5
- data/features/skip_some_routes.feature +32 -19
- data/features/step_definitions/request_steps.rb +10 -0
- data/features/step_definitions/response_steps.rb +2 -2
- data/features/step_definitions/setup_steps.rb +21 -9
- data/features/variation_on_field_name.feature +3 -3
- data/lib/rack/csrf.rb +18 -5
- data/rack_csrf.gemspec +4 -3
- data/spec/csrf_spec.rb +2 -0
- metadata +4 -3
data/Manifest
CHANGED
data/README.rdoc
CHANGED
@@ -27,17 +27,32 @@ The following options allow you to tweak Rack::Csrf.
|
|
27
27
|
an empty response, Rack::Csrf will raise an exception of class
|
28
28
|
Rack::Csrf::InvalidCsrfToken.
|
29
29
|
|
30
|
+
Default value: false.
|
31
|
+
|
30
32
|
[<tt>:skip</tt>]
|
31
33
|
By default, Rack::Csrf checks every POST, PUT and DELETE request; passing an
|
32
|
-
array of HTTP method/URL to this option you
|
33
|
-
unchecked:
|
34
|
+
array of HTTP method/URL (regular expressions allowed) to this option you
|
35
|
+
can choose what to let pass unchecked:
|
36
|
+
|
37
|
+
use Rack::Csrf, :skip => ['POST:/not_checking', 'PUT:/me_too',
|
38
|
+
'DELETE:/cars/.*\.xml']
|
39
|
+
|
40
|
+
Please, note that the regular expressions are not escaped and it is your
|
41
|
+
duty to write them correctly.
|
34
42
|
|
35
|
-
|
43
|
+
Default value: empty.
|
36
44
|
|
37
45
|
[<tt>:field</tt>]
|
38
46
|
Default field name (see below) is <tt>_csrf</tt>; you can adapt it to
|
39
47
|
specific needs.
|
40
48
|
|
49
|
+
[<tt>:browser_only</tt>]
|
50
|
+
Set it to true to inspect only requests with Content-Type typically produced
|
51
|
+
only by web browser. This means that curl, Active Resource, etc. can send
|
52
|
+
any request without worring about the token.
|
53
|
+
|
54
|
+
Default value: false.
|
55
|
+
|
41
56
|
== Helpers
|
42
57
|
|
43
58
|
The following class methods try to ease the insertion of the anti-forging
|
data/Rakefile
CHANGED
@@ -11,7 +11,7 @@ Spec::Rake::SpecTask.new do |t|
|
|
11
11
|
t.spec_opts = %w(-O spec/spec.opts)
|
12
12
|
end
|
13
13
|
|
14
|
-
Echoe.new('rack_csrf', '1.0
|
14
|
+
Echoe.new('rack_csrf', '1.1.0') do |s|
|
15
15
|
s.author = 'Emanuele Vicentini'
|
16
16
|
s.email = 'emanuele.vicentini@gmail.com'
|
17
17
|
s.summary = 'Anti-CSRF Rack middleware'
|
data/cucumber.yml
CHANGED
@@ -1 +1 @@
|
|
1
|
-
default: --format pretty -
|
1
|
+
default: --format pretty --no-source features
|
data/example/config.ru
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
Feature: Filtering only browser generated requests
|
2
|
+
|
3
|
+
Scenario Outline: Handling request without CSRF token
|
4
|
+
Given a rack with the anti-CSRF middleware and the :browser_only option
|
5
|
+
When it receives a <method> request without the CSRF token
|
6
|
+
Then it lets it pass untouched
|
7
|
+
|
8
|
+
Examples:
|
9
|
+
| method |
|
10
|
+
| POST |
|
11
|
+
| PUT |
|
12
|
+
| DELETE |
|
13
|
+
|
14
|
+
Scenario Outline: Handling request without CSRF token
|
15
|
+
Given a rack with the anti-CSRF middleware and the :browser_only option
|
16
|
+
When it receives a <method> request without the CSRF token from a browser
|
17
|
+
Then it responds with 417
|
18
|
+
And the response body is empty
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
| method |
|
22
|
+
| POST |
|
23
|
+
| PUT |
|
24
|
+
| DELETE |
|
@@ -1,17 +1,17 @@
|
|
1
1
|
Feature: Handling of the HTTP requests returning an empty response
|
2
2
|
|
3
3
|
Scenario: GET request with CSRF token
|
4
|
-
Given a
|
4
|
+
Given a rack with the anti-CSRF middleware
|
5
5
|
When it receives a GET request with the CSRF token
|
6
6
|
Then it lets it pass untouched
|
7
7
|
|
8
8
|
Scenario: GET request without CSRF token
|
9
|
-
Given a
|
9
|
+
Given a rack with the anti-CSRF middleware
|
10
10
|
When it receives a GET request without the CSRF token
|
11
11
|
Then it lets it pass untouched
|
12
12
|
|
13
13
|
Scenario Outline: Handling request without CSRF token
|
14
|
-
Given a
|
14
|
+
Given a rack with the anti-CSRF middleware
|
15
15
|
When it receives a <method> request without the CSRF token
|
16
16
|
Then it responds with 417
|
17
17
|
And the response body is empty
|
@@ -23,7 +23,7 @@ Feature: Handling of the HTTP requests returning an empty response
|
|
23
23
|
| DELETE |
|
24
24
|
|
25
25
|
Scenario Outline: Handling request with the right CSRF token
|
26
|
-
Given a
|
26
|
+
Given a rack with the anti-CSRF middleware
|
27
27
|
When it receives a <method> request with the right CSRF token
|
28
28
|
Then it lets it pass untouched
|
29
29
|
|
@@ -34,7 +34,7 @@ Feature: Handling of the HTTP requests returning an empty response
|
|
34
34
|
| DELETE |
|
35
35
|
|
36
36
|
Scenario Outline: Handling request with the wrong CSRF token
|
37
|
-
Given a
|
37
|
+
Given a rack with the anti-CSRF middleware
|
38
38
|
When it receives a <method> request with the wrong CSRF token
|
39
39
|
Then it responds with 417
|
40
40
|
And the response body is empty
|
@@ -1,12 +1,12 @@
|
|
1
1
|
Feature: Handling of the HTTP requests raising an exception
|
2
2
|
|
3
3
|
Scenario: GET request without CSRF token
|
4
|
-
Given a
|
4
|
+
Given a rack with the anti-CSRF middleware and the :raise option
|
5
5
|
When it receives a GET request without the CSRF token
|
6
6
|
Then it lets it pass untouched
|
7
7
|
|
8
8
|
Scenario Outline: Handling request without CSRF token
|
9
|
-
Given a
|
9
|
+
Given a rack with the anti-CSRF middleware and the :raise option
|
10
10
|
When it receives a <method> request without the CSRF token
|
11
11
|
Then there is no response
|
12
12
|
And an exception is climbing up the stack
|
@@ -18,7 +18,7 @@ Feature: Handling of the HTTP requests raising an exception
|
|
18
18
|
| DELETE |
|
19
19
|
|
20
20
|
Scenario Outline: Handling request with the right CSRF token
|
21
|
-
Given a
|
21
|
+
Given a rack with the anti-CSRF middleware and the :raise option
|
22
22
|
When it receives a <method> request with the right CSRF token
|
23
23
|
Then it lets it pass untouched
|
24
24
|
|
@@ -29,7 +29,7 @@ Feature: Handling of the HTTP requests raising an exception
|
|
29
29
|
| DELETE |
|
30
30
|
|
31
31
|
Scenario Outline: Handling request with the wrong CSRF token
|
32
|
-
Given a
|
32
|
+
Given a rack with the anti-CSRF middleware and the :raise option
|
33
33
|
When it receives a <method> request with the wrong CSRF token
|
34
34
|
Then there is no response
|
35
35
|
And an exception is climbing up the stack
|
data/features/setup.feature
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
Feature: Setup of the middleware
|
2
2
|
|
3
3
|
Scenario: Simple setup with session support
|
4
|
-
Given a
|
4
|
+
Given a rack with the session middleware
|
5
5
|
When I insert the anti-CSRF middleware
|
6
6
|
Then I get a fully functional rack
|
7
7
|
|
8
8
|
Scenario: Simple setup without session support
|
9
|
-
Given a
|
9
|
+
Given a rack without the session middleware
|
10
10
|
When I insert the anti-CSRF middleware
|
11
11
|
Then I get an error message
|
12
12
|
|
13
13
|
Scenario: Setup with :raise option
|
14
|
-
Given a
|
14
|
+
Given a rack with the session middleware
|
15
15
|
When I insert the anti-CSRF middleware with the :raise option
|
16
16
|
Then I get a fully functional rack
|
17
17
|
|
18
18
|
Scenario: Setup with the :skip option
|
19
|
-
Given a
|
19
|
+
Given a rack with the session middleware
|
20
20
|
When I insert the anti-CSRF middleware with the :skip option
|
21
21
|
| route |
|
22
22
|
| POST:/not_checking |
|
@@ -24,6 +24,11 @@ Feature: Setup of the middleware
|
|
24
24
|
Then I get a fully functional rack
|
25
25
|
|
26
26
|
Scenario: Setup with the :field option
|
27
|
-
Given a
|
27
|
+
Given a rack with the session middleware
|
28
28
|
When I insert the anti-CSRF middleware with the :field option
|
29
29
|
Then I get a fully functional rack
|
30
|
+
|
31
|
+
Scenario: Setup with the :browser_only option
|
32
|
+
Given a rack with the session middleware
|
33
|
+
When I insert the anti-CSRF middleware with the :browser_only option
|
34
|
+
Then I get a fully functional rack
|
@@ -1,33 +1,46 @@
|
|
1
1
|
Feature: Skipping the check for some specific routes
|
2
2
|
|
3
3
|
Scenario Outline: Skipping the check for some requests
|
4
|
-
Given a
|
5
|
-
| pair
|
6
|
-
| POST:/not_checking
|
7
|
-
| PUT:/is_wrong
|
4
|
+
Given a rack with the anti-CSRF middleware and the :skip option
|
5
|
+
| pair |
|
6
|
+
| POST:/not_checking |
|
7
|
+
| PUT:/is_wrong |
|
8
|
+
| POST:/not_.*\.json |
|
9
|
+
| DELETE:/cars/.*\.xml |
|
8
10
|
When it receives a <method> request for <path> without the CSRF token
|
9
11
|
Then it lets it pass untouched
|
10
12
|
|
11
13
|
Examples:
|
12
|
-
| method | path
|
13
|
-
| POST | /not_checking
|
14
|
-
| PUT | /is_wrong
|
14
|
+
| method | path |
|
15
|
+
| POST | /not_checking |
|
16
|
+
| PUT | /is_wrong |
|
17
|
+
| POST | /not_checking.json |
|
18
|
+
| POST | /not_again/params/whatever.json |
|
19
|
+
| DELETE | /cars/abc123.xml |
|
15
20
|
|
16
21
|
Scenario Outline: Keep checking the requests for other method/path pairs
|
17
|
-
Given a
|
18
|
-
| pair
|
19
|
-
| POST:/not_checking
|
20
|
-
| PUT:/is_wrong
|
22
|
+
Given a rack with the anti-CSRF middleware and the :skip option
|
23
|
+
| pair |
|
24
|
+
| POST:/not_checking |
|
25
|
+
| PUT:/is_wrong |
|
26
|
+
| POST:/not_.*\.json |
|
27
|
+
| DELETE:/cars/.*\.xml |
|
21
28
|
When it receives a <method> request for <path> without the CSRF token
|
22
29
|
Then it responds with 417
|
23
30
|
And the response body is empty
|
24
31
|
|
25
32
|
Examples:
|
26
|
-
| method | path
|
27
|
-
| PUT | /not_checking
|
28
|
-
| DELETE | /not_checking
|
29
|
-
| POST | /is_wrong
|
30
|
-
| DELETE | /is_wrong
|
31
|
-
| POST | /
|
32
|
-
| PUT | /not
|
33
|
-
| POST | /is
|
33
|
+
| method | path |
|
34
|
+
| PUT | /not_checking |
|
35
|
+
| DELETE | /not_checking |
|
36
|
+
| POST | /is_wrong |
|
37
|
+
| DELETE | /is_wrong |
|
38
|
+
| POST | / |
|
39
|
+
| PUT | /not |
|
40
|
+
| POST | /is |
|
41
|
+
| PUT | /not_checking.json |
|
42
|
+
| DELETE | /not_checking.json |
|
43
|
+
| PUT | /not_again/params/whatever.json |
|
44
|
+
| DELETE | /not_again/params/whatever.json |
|
45
|
+
| POST | /cars/abc123.xml |
|
46
|
+
| PUT | /cars/abc123.xml |
|
@@ -28,6 +28,16 @@ When /^it receives a (POST|PUT|DELETE) request for (.+) without the CSRF token$/
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
When /^it receives a (POST|PUT|DELETE) request without the CSRF token from a browser$/ do |http_method|
|
32
|
+
http_method.downcase!
|
33
|
+
begin
|
34
|
+
@response = Rack::MockRequest.new(@app).send http_method.to_sym, '/',
|
35
|
+
'CONTENT_TYPE' => 'text/html'
|
36
|
+
rescue Exception => e
|
37
|
+
@exception = e
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
31
41
|
When /^it receives a (POST|PUT|DELETE) request with the right CSRF token$/ do |http_method|
|
32
42
|
http_method.downcase!
|
33
43
|
@response = Rack::MockRequest.new(@app).send http_method.to_sym, '/',
|
@@ -3,8 +3,8 @@ Then /^it lets it pass untouched$/ do
|
|
3
3
|
@response.should =~ /Hello world!/
|
4
4
|
end
|
5
5
|
|
6
|
-
Then /^it responds with
|
7
|
-
@response.status.should ==
|
6
|
+
Then /^it responds with (\d\d\d)$/ do |code|
|
7
|
+
@response.status.should == code.to_i
|
8
8
|
end
|
9
9
|
|
10
10
|
Then /^the response body is empty$/ do
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Given /^a
|
1
|
+
Given /^a rack (with|without) the session middleware$/ do |prep|
|
2
2
|
@rack_builder = Rack::Builder.new
|
3
3
|
@rack_builder.use FakeSession if prep == 'with'
|
4
4
|
end
|
@@ -6,26 +6,31 @@ end
|
|
6
6
|
# Yes, they're not as DRY as possible, but I think they're more readable than
|
7
7
|
# a single step definition with a few captures and more complex checkings.
|
8
8
|
|
9
|
-
Given /^a
|
10
|
-
Given 'a
|
9
|
+
Given /^a rack with the anti\-CSRF middleware$/ do
|
10
|
+
Given 'a rack with the session middleware'
|
11
11
|
When 'I insert the anti-CSRF middleware'
|
12
12
|
end
|
13
13
|
|
14
|
-
Given /^a
|
15
|
-
Given 'a
|
14
|
+
Given /^a rack with the anti\-CSRF middleware and the :raise option$/ do
|
15
|
+
Given 'a rack with the session middleware'
|
16
16
|
When 'I insert the anti-CSRF middleware with the :raise option'
|
17
17
|
end
|
18
18
|
|
19
|
-
Given /^a
|
20
|
-
Given 'a
|
19
|
+
Given /^a rack with the anti\-CSRF middleware and the :skip option$/ do |table|
|
20
|
+
Given 'a rack with the session middleware'
|
21
21
|
When 'I insert the anti-CSRF middleware with the :skip option', table
|
22
22
|
end
|
23
23
|
|
24
|
-
Given /^a
|
25
|
-
Given 'a
|
24
|
+
Given /^a rack with the anti\-CSRF middleware and the :field option$/ do
|
25
|
+
Given 'a rack with the session middleware'
|
26
26
|
When 'I insert the anti-CSRF middleware with the :field option'
|
27
27
|
end
|
28
28
|
|
29
|
+
Given /^a rack with the anti\-CSRF middleware and the :browser_only option$/ do
|
30
|
+
Given 'a rack with the session middleware'
|
31
|
+
When 'I insert the anti-CSRF middleware with the :browser_only option'
|
32
|
+
end
|
33
|
+
|
29
34
|
# Yes, they're not as DRY as possible, but I think they're more readable than
|
30
35
|
# a single step definition with a few captures and more complex checkings.
|
31
36
|
|
@@ -58,6 +63,13 @@ When /^I insert the anti\-CSRF middleware with the :field option$/ do
|
|
58
63
|
@app = @rack_builder.to_app
|
59
64
|
end
|
60
65
|
|
66
|
+
When /^I insert the anti\-CSRF middleware with the :browser_only option$/ do
|
67
|
+
@rack_builder.use Rack::Lint
|
68
|
+
@rack_builder.use Rack::Csrf, :browser_only => true
|
69
|
+
@rack_builder.run(lambda {|env| Rack::Response.new('Hello world!').finish})
|
70
|
+
@app = @rack_builder.to_app
|
71
|
+
end
|
72
|
+
|
61
73
|
Then /^I get a fully functional rack$/ do
|
62
74
|
lambda {Rack::MockRequest.new(@app).get('/')}.should_not raise_error
|
63
75
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
Feature: Customization of the field name
|
2
2
|
|
3
3
|
Scenario: GET request with CSRF token in custom field
|
4
|
-
Given a
|
4
|
+
Given a rack with the anti-CSRF middleware and the :field option
|
5
5
|
When it receives a GET request with the CSRF token
|
6
6
|
Then it lets it pass untouched
|
7
7
|
|
8
8
|
Scenario Outline: Handling request with the right CSRF token in custom field
|
9
|
-
Given a
|
9
|
+
Given a rack with the anti-CSRF middleware and the :field option
|
10
10
|
When it receives a <method> request with the right CSRF token
|
11
11
|
Then it lets it pass untouched
|
12
12
|
|
@@ -17,7 +17,7 @@ Feature: Customization of the field name
|
|
17
17
|
| DELETE |
|
18
18
|
|
19
19
|
Scenario Outline: Handling request with the wrong CSRF token in custom field
|
20
|
-
Given a
|
20
|
+
Given a rack with the anti-CSRF middleware and the :field option
|
21
21
|
When it receives a <method> request with the wrong CSRF token
|
22
22
|
Then it responds with 417
|
23
23
|
And the response body is empty
|
data/lib/rack/csrf.rb
CHANGED
@@ -14,10 +14,17 @@ module Rack
|
|
14
14
|
|
15
15
|
def initialize(app, opts = {})
|
16
16
|
@app = app
|
17
|
+
|
17
18
|
@raisable = opts[:raise] || false
|
18
|
-
@skippable = opts[:skip] || []
|
19
|
-
@skippable.map {|s| s.downcase!}
|
19
|
+
@skippable = (opts[:skip] || []).map {|r| /\A#{r}\Z/i}
|
20
20
|
@@field = opts[:field] if opts[:field]
|
21
|
+
@browser_only = opts[:browser_only] || false
|
22
|
+
|
23
|
+
@http_verbs = %w(POST PUT DELETE)
|
24
|
+
@browser_content_types = ['text/html', 'application/xhtml+xml', 'xhtml',
|
25
|
+
'application/x-www-form-urlencoded',
|
26
|
+
'multipart/form-data',
|
27
|
+
'text/plain', 'txt']
|
21
28
|
end
|
22
29
|
|
23
30
|
def call(env)
|
@@ -26,9 +33,9 @@ module Rack
|
|
26
33
|
end
|
27
34
|
self.class.csrf_token(env)
|
28
35
|
req = Rack::Request.new(env)
|
29
|
-
untouchable =
|
36
|
+
untouchable = !@http_verbs.include?(req.request_method) ||
|
30
37
|
req.POST[self.class.csrf_field] == env['rack.session']['csrf.token'] ||
|
31
|
-
skip_checking(req)
|
38
|
+
skip_checking(req) || (@browser_only && !from_a_browser(req))
|
32
39
|
if untouchable
|
33
40
|
@app.call(env)
|
34
41
|
else
|
@@ -52,7 +59,13 @@ module Rack
|
|
52
59
|
protected
|
53
60
|
|
54
61
|
def skip_checking request
|
55
|
-
@skippable.
|
62
|
+
@skippable.any? do |route|
|
63
|
+
route =~ (request.request_method + ':' + request.path_info)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def from_a_browser request
|
68
|
+
@browser_content_types.include?(request.media_type)
|
56
69
|
end
|
57
70
|
end
|
58
71
|
end
|
data/rack_csrf.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack_csrf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emanuele Vicentini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
|
11
|
-
date: 2009-05-
|
11
|
+
date: 2009-05-22 00:00:00 +02:00
|
12
12
|
default_executable:
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- example/views/form.erb
|
79
79
|
- example/views/form_not_working.erb
|
80
80
|
- example/views/response.erb
|
81
|
+
- features/browser_only.feature
|
81
82
|
- features/empty_responses.feature
|
82
83
|
- features/raising_exception.feature
|
83
84
|
- features/setup.feature
|
@@ -126,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
127
|
requirements: []
|
127
128
|
|
128
129
|
rubyforge_project: rackcsrf
|
129
|
-
rubygems_version: 1.3.
|
130
|
+
rubygems_version: 1.3.3
|
130
131
|
specification_version: 3
|
131
132
|
summary: Anti-CSRF Rack middleware
|
132
133
|
test_files: []
|
data/spec/csrf_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack_csrf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emanuele Vicentini
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-22 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -79,6 +79,7 @@ files:
|
|
79
79
|
- example/views/form.erb
|
80
80
|
- example/views/form_not_working.erb
|
81
81
|
- example/views/response.erb
|
82
|
+
- features/browser_only.feature
|
82
83
|
- features/empty_responses.feature
|
83
84
|
- features/raising_exception.feature
|
84
85
|
- features/setup.feature
|
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
128
|
requirements: []
|
128
129
|
|
129
130
|
rubyforge_project: rackcsrf
|
130
|
-
rubygems_version: 1.3.
|
131
|
+
rubygems_version: 1.3.3
|
131
132
|
signing_key:
|
132
133
|
specification_version: 3
|
133
134
|
summary: Anti-CSRF Rack middleware
|