rack_csrf 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest CHANGED
@@ -5,6 +5,7 @@ example/config.ru
5
5
  example/views/form.erb
6
6
  example/views/form_not_working.erb
7
7
  example/views/response.erb
8
+ features/browser_only.feature
8
9
  features/empty_responses.feature
9
10
  features/raising_exception.feature
10
11
  features/setup.feature
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 can choose what to let pass
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
- use Rack::Csrf, :skip => ['POST:/not_checking', 'PUT:/me_too']
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.1') do |s|
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 -n features
1
+ default: --format pretty --no-source features
@@ -1,6 +1,9 @@
1
1
  require 'sinatra'
2
2
  require File.dirname(__FILE__) + '/../lib/rack/csrf'
3
3
 
4
+ require 'erb'
5
+ require 'app'
6
+
4
7
  use Rack::ShowExceptions
5
8
  use Rack::Session::Cookie
6
9
  use Rack::Csrf, :raise => true
data/example/config.ru CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'sinatra'
2
2
  require File.dirname(__FILE__) + '/../lib/rack/csrf'
3
3
 
4
+ require 'erb'
5
+ require 'app'
6
+
4
7
  use Rack::Session::Cookie
5
8
  use Rack::Csrf
6
9
 
@@ -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 Rack setup with the anti-CSRF middleware
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 Rack setup with the anti-CSRF middleware
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 Rack setup with the anti-CSRF middleware
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 Rack setup with the anti-CSRF middleware
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 Rack setup with the anti-CSRF middleware
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 Rack setup with the anti-CSRF middleware and the :raise option
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 Rack setup with the anti-CSRF middleware and the :raise option
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 Rack setup with the anti-CSRF middleware and the :raise option
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 Rack setup with the anti-CSRF middleware and the :raise option
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
@@ -1,22 +1,22 @@
1
1
  Feature: Setup of the middleware
2
2
 
3
3
  Scenario: Simple setup with session support
4
- Given a Rack setup with the session middleware
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 Rack setup without the session middleware
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 Rack setup with the session middleware
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 Rack setup with the session middleware
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 Rack setup with the session middleware
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 Rack setup with the anti-CSRF middleware and the :skip option
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 Rack setup with the anti-CSRF middleware and the :skip option
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 417$/ do
7
- @response.status.should == 417
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 Rack setup (with|without) the session middleware$/ do |prep|
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 Rack setup with the anti\-CSRF middleware$/ do
10
- Given 'a Rack setup with the session middleware'
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 Rack setup with the anti\-CSRF middleware and the :raise option$/ do
15
- Given 'a Rack setup with the session middleware'
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 Rack setup with the anti\-CSRF middleware and the :skip option$/ do |table|
20
- Given 'a Rack setup with the session middleware'
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 Rack setup with the anti\-CSRF middleware and the :field option$/ do
25
- Given 'a Rack setup with the session middleware'
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 Rack setup with the anti-CSRF middleware and the :field option
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 Rack setup with the anti-CSRF middleware and the :field option
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 Rack setup with the anti-CSRF middleware and the :field option
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 = !%w(POST PUT DELETE).include?(req.request_method) ||
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.include?(request.request_method.downcase + ':' + request.path_info)
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.1
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-02 00:00:00 +02:00
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.2
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
@@ -40,6 +40,8 @@ describe Rack::Csrf do
40
40
  describe '#csrf_tag' do
41
41
  before do
42
42
  @env = {'rack.session' => {}}
43
+ fakeapp = [200, {}, []]
44
+ Rack::Csrf.new fakeapp, :field => 'whatever'
43
45
  @tag = Rack::Csrf.csrf_tag(@env)
44
46
  end
45
47
 
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.1
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-02 00:00:00 +02:00
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.2
131
+ rubygems_version: 1.3.3
131
132
  signing_key:
132
133
  specification_version: 3
133
134
  summary: Anti-CSRF Rack middleware