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 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