rack_csrf 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ Feature: Skipping the check if a block passes
2
+
3
+ Scenario Outline: Skipping check for requests with specific header
4
+ Given a rack with the anti-CSRF middleware and the :skip_if option
5
+ | name | value |
6
+ | token | skip |
7
+ | User-Agent | MSIE |
8
+ When it receives a request with headers <name> = <value> without the CSRF token or header
9
+ Then it lets it pass untouched
10
+
11
+ Examples:
12
+ | name | value |
13
+ | token | skip |
14
+ | User-Agent | MSIE |
15
+
16
+ Scenario Outline: Not skipping check for requests without specific header
17
+ Given a rack with the anti-CSRF middleware and the :skip_if option
18
+ | name | value |
19
+ | token | skip |
20
+ | User-Agent | MSIE |
21
+ When it receives a request with headers <name> = <value> without the CSRF token or header
22
+ Then it responds with 403
23
+
24
+ Examples:
25
+ | name | value |
26
+ | token | forward |
27
+ | User-Agent | WebKit |
28
+ | User-Agent | Mozilla |
29
+
30
+ Scenario Outline: Skipping check for requests with :skip and :skip_if options
31
+ Given a rack with the anti-CSRF middleware and both the :skip and :skip_if options
32
+ | name | value | path |
33
+ | token | skip | POST:/ |
34
+ When it receives a request with headers <name> = <value>, <method>, and without the CSRF token or header
35
+ Then it lets it pass untouched
36
+
37
+ Examples:
38
+ | name | value | method |
39
+ | token | skip | POST |
40
+ | token | pass | POST |
@@ -8,7 +8,7 @@ Feature: Skipping the check for some specific routes
8
8
  | POST:/not_.*\.json |
9
9
  | DELETE:/cars/.*\.xml |
10
10
  | PATCH:/this/one/too |
11
- When it receives a <method> request for <path> without the CSRF token
11
+ When it receives a <method> request for <path> without the CSRF token or header
12
12
  Then it lets it pass untouched
13
13
 
14
14
  Examples:
@@ -28,7 +28,7 @@ Feature: Skipping the check for some specific routes
28
28
  | POST:/not_.*\.json |
29
29
  | DELETE:/cars/.*\.xml |
30
30
  | PATCH:/this/one/too |
31
- When it receives a <method> request for <path> without the CSRF token
31
+ When it receives a <method> request for <path> without the CSRF token or header
32
32
  Then it responds with 403
33
33
  And the response body is empty
34
34
 
@@ -63,7 +63,7 @@ Feature: Skipping the check for some specific routes
63
63
  | PUT:/ |
64
64
  | DELETE:/ |
65
65
  | PATCH:/ |
66
- When it receives a <method> request with neither PATH_INFO nor CSRF token
66
+ When it receives a <method> request with neither PATH_INFO nor CSRF token or header
67
67
  Then it lets it pass untouched
68
68
 
69
69
  Examples:
@@ -1,7 +1,7 @@
1
1
  # Yes, they're not as DRY as possible, but I think they're more readable than
2
2
  # a single step definition with a few captures and more complex checkings.
3
3
 
4
- When /^it receives a (.*) request without the CSRF token$/ do |http_method|
4
+ When /^it receives a (.*) request without the CSRF (?:token|header)$/ do |http_method|
5
5
  begin
6
6
  @browser.request '/', :method => http_method
7
7
  rescue Exception => e
@@ -9,7 +9,7 @@ When /^it receives a (.*) request without the CSRF token$/ do |http_method|
9
9
  end
10
10
  end
11
11
 
12
- When /^it receives a (.*) request for (.+) without the CSRF token$/ do |http_method, path|
12
+ When /^it receives a (.*) request for (.+) without the CSRF (?:token|header|token or header)$/ do |http_method, path|
13
13
  begin
14
14
  @browser.request path, :method => http_method
15
15
  rescue Exception => e
@@ -18,24 +18,56 @@ When /^it receives a (.*) request for (.+) without the CSRF token$/ do |http_met
18
18
  end
19
19
 
20
20
  When /^it receives a (.*) request with the right CSRF token$/ do |http_method|
21
- @browser.request '/', :method => http_method,
22
- 'rack.session' => {Rack::Csrf.key => 'right_token'},
23
- :params => {Rack::Csrf.field => 'right_token'}
21
+ @browser.request '/', :method => http_method,
22
+ 'rack.session' => {Rack::Csrf.key => 'right_token'},
23
+ :params => {Rack::Csrf.field => 'right_token'}
24
+ end
25
+
26
+ When /^it receives a (.*) request with the right CSRF header$/ do |http_method|
27
+ @browser.request '/', :method => http_method,
28
+ 'rack.session' => {Rack::Csrf.key => 'right_token'},
29
+ Rack::Csrf.rackified_header => 'right_token'
24
30
  end
25
31
 
26
32
  When /^it receives a (.*) request with the wrong CSRF token$/ do |http_method|
33
+ begin
34
+ @browser.request '/', :method => http_method,
35
+ 'rack.session' => {Rack::Csrf.key => 'right_token'},
36
+ :params => {Rack::Csrf.field => 'wrong_token'}
37
+ rescue Exception => e
38
+ @exception = e
39
+ end
40
+ end
41
+
42
+ When /^it receives a (.*) request with the wrong CSRF header/ do |http_method|
27
43
  begin
28
44
  @browser.request '/', :method => http_method,
29
- :params => {Rack::Csrf.field => 'whatever'}
45
+ Rack::Csrf.rackified_header => 'right_token'
30
46
  rescue Exception => e
31
47
  @exception = e
32
48
  end
33
49
  end
34
50
 
35
- When /^it receives a (.*) request with neither PATH_INFO nor CSRF token$/ do |http_method|
51
+ When /^it receives a (.*) request with neither PATH_INFO nor CSRF token or header$/ do |http_method|
36
52
  begin
37
53
  @browser.request '/doesntmatter', :method => http_method, 'PATH_INFO' => ''
38
54
  rescue Exception => e
39
55
  @exception = e
40
56
  end
41
57
  end
58
+
59
+ When /^it receives a request with headers (.+) = ([^ ]+) without the CSRF token or header$/ do |name, value|
60
+ begin
61
+ @browser.request '/', Hash[:method, 'POST', name, value]
62
+ rescue Exception => e
63
+ @exception = e
64
+ end
65
+ end
66
+
67
+ When /^it receives a request with headers (.+) = ([^,]+), (.+), and without the CSRF token or header$/ do |name, value, method|
68
+ begin
69
+ @browser.request '/', Hash[:method, method, name, value]
70
+ rescue Exception => e
71
+ @exception = e
72
+ end
73
+ end
@@ -8,38 +8,53 @@ end
8
8
  # a single step definition with a few captures and more complex checkings.
9
9
 
10
10
  Given /^a rack with the anti\-CSRF middleware$/ do
11
- Given 'a rack with the session middleware'
12
- When 'I insert the anti-CSRF middleware'
11
+ step 'a rack with the session middleware'
12
+ step 'I insert the anti-CSRF middleware'
13
13
  end
14
14
 
15
15
  Given /^a rack with the anti\-CSRF middleware and the :raise option$/ do
16
- Given 'a rack with the session middleware'
17
- When 'I insert the anti-CSRF middleware with the :raise option'
16
+ step 'a rack with the session middleware'
17
+ step 'I insert the anti-CSRF middleware with the :raise option'
18
18
  end
19
19
 
20
20
  Given /^a rack with the anti\-CSRF middleware and the :skip option$/ do |table|
21
- Given 'a rack with the session middleware'
22
- When 'I insert the anti-CSRF middleware with the :skip option', table
21
+ step 'a rack with the session middleware'
22
+ step 'I insert the anti-CSRF middleware with the :skip option', table
23
+ end
24
+
25
+ Given /^a rack with the anti\-CSRF middleware and the :skip_if option$/ do |table|
26
+ step 'a rack with the session middleware'
27
+ step 'I insert the anti-CSRF middleware with the :skip_if option', table
28
+ end
29
+
30
+ Given /^a rack with the anti\-CSRF middleware and both the :skip and :skip_if options$/ do |table|
31
+ step 'a rack with the session middleware'
32
+ step 'I insert the anti-CSRF middleware with the :skip and :skip_if options', table
23
33
  end
24
34
 
25
35
  Given /^a rack with the anti\-CSRF middleware and the :field option$/ do
26
- Given 'a rack with the session middleware'
27
- When 'I insert the anti-CSRF middleware with the :field option'
36
+ step 'a rack with the session middleware'
37
+ step 'I insert the anti-CSRF middleware with the :field option'
28
38
  end
29
39
 
30
40
  Given /^a rack with the anti\-CSRF middleware and the :key option$/ do
31
- Given 'a rack with the session middleware'
32
- When 'I insert the anti-CSRF middleware with the :key option'
41
+ step 'a rack with the session middleware'
42
+ step 'I insert the anti-CSRF middleware with the :key option'
43
+ end
44
+
45
+ Given /^a rack with the anti\-CSRF middleware and the :header option$/ do
46
+ step 'a rack with the session middleware'
47
+ step 'I insert the anti-CSRF middleware with the :header option'
33
48
  end
34
49
 
35
50
  Given /^a rack with the anti\-CSRF middleware and the :check_also option$/ do |table|
36
- Given 'a rack with the session middleware'
37
- When 'I insert the anti-CSRF middleware with the :check_also option', table
51
+ step 'a rack with the session middleware'
52
+ step 'I insert the anti-CSRF middleware with the :check_also option', table
38
53
  end
39
54
 
40
55
  Given /^a rack with the anti\-CSRF middleware and the :check_only option$/ do |table|
41
- Given 'a rack with the session middleware'
42
- When 'I insert the anti-CSRF middleware with the :check_only option', table
56
+ step 'a rack with the session middleware'
57
+ step 'I insert the anti-CSRF middleware with the :check_only option', table
43
58
  end
44
59
 
45
60
  # Yes, they're not as DRY as possible, but I think they're more readable than
@@ -64,6 +79,27 @@ When /^I insert the anti\-CSRF middleware with the :skip option$/ do |table|
64
79
  @browser = Rack::Test::Session.new(Rack::MockSession.new(@app))
65
80
  end
66
81
 
82
+ When /^I insert the anti\-CSRF middleware with the :skip_if option$/ do |table|
83
+ skippable = table.hashes.collect {|t| t.values}
84
+ @rack_builder.use Rack:: Csrf, :skip_if => Proc.new { |request|
85
+ skippable.any? { |name, value| request.env[name] == value }
86
+ }
87
+ @app = toy_app
88
+ @browser = Rack::Test::Session.new(Rack::MockSession.new(@app))
89
+ end
90
+
91
+ When /^I insert the anti\-CSRF middleware with the :skip and :skip_if options$/ do |table|
92
+ data = table.hashes.collect {|t| t.values}[0]
93
+ headers = data[0..1]
94
+ skippable = data[2]
95
+
96
+ @rack_builder.use Rack:: Csrf, :skip => [skippable], :skip_if => Proc.new { |request|
97
+ skippable.any? { |name, value| request.env[name] == value }
98
+ }
99
+ @app = toy_app
100
+ @browser = Rack::Test::Session.new(Rack::MockSession.new(@app))
101
+ end
102
+
67
103
  When /^I insert the anti\-CSRF middleware with the :field option$/ do
68
104
  @rack_builder.use Rack::Csrf, :field => 'fantasy_name'
69
105
  @app = toy_app
@@ -76,6 +112,12 @@ When /^I insert the anti\-CSRF middleware with the :key option$/ do
76
112
  @browser = Rack::Test::Session.new(Rack::MockSession.new(@app))
77
113
  end
78
114
 
115
+ When /^I insert the anti\-CSRF middleware with the :header option$/ do
116
+ @rack_builder.use Rack::Csrf, :header => 'fantasy_name'
117
+ @app = toy_app
118
+ @browser = Rack::Test::Session.new(Rack::MockSession.new(@app))
119
+ end
120
+
79
121
  When /^I insert the anti\-CSRF middleware with the :check_also option$/ do |table|
80
122
  check_also = table.hashes.collect {|t| t.values}.flatten
81
123
  @rack_builder.use Rack::Csrf, :check_also => check_also
@@ -0,0 +1,35 @@
1
+ Feature: Customization of the header name
2
+
3
+ Background:
4
+ Given a rack with the anti-CSRF middleware and the :header option
5
+
6
+ Scenario: GET request with the right CSRF header in custom field
7
+ When it receives a GET request with the right CSRF header
8
+ Then it lets it pass untouched
9
+
10
+ Scenario: GET request with the wrong CSRF header in custom field
11
+ When it receives a GET request with the wrong CSRF header
12
+ Then it lets it pass untouched
13
+
14
+ Scenario Outline: Handling request with the right CSRF header in custom field
15
+ When it receives a <method> request with the right CSRF header
16
+ Then it lets it pass untouched
17
+
18
+ Examples:
19
+ | method |
20
+ | POST |
21
+ | PUT |
22
+ | DELETE |
23
+ | PATCH |
24
+
25
+ Scenario Outline: Handling request with the wrong CSRF header in custom field
26
+ When it receives a <method> request with the wrong CSRF header
27
+ Then it responds with 403
28
+ And the response body is empty
29
+
30
+ Examples:
31
+ | method |
32
+ | POST |
33
+ | PUT |
34
+ | DELETE |
35
+ | PATCH |
@@ -10,20 +10,23 @@ module Rack
10
10
  class SessionUnavailable < StandardError; end
11
11
  class InvalidCsrfToken < StandardError; end
12
12
 
13
- @@field = '_csrf'
14
- @@key = 'csrf.token'
13
+ @@field = '_csrf'
14
+ @@header = 'X_CSRF_TOKEN'
15
+ @@key = 'csrf.token'
15
16
 
16
17
  def initialize(app, opts = {})
17
18
  @app = app
18
19
 
19
- @raisable = opts[:raise] || false
20
- @to_be_skipped = (opts[:skip] || []).map {|r| /\A#{r}\Z/i}
21
- @to_be_checked = (opts[:check_only] || []).map {|r| /\A#{r}\Z/i}
22
- @@field = opts[:field] if opts[:field]
23
- @@key = opts[:key] if opts[:key]
20
+ @raisable = opts[:raise] || false
21
+ @skip_list = (opts[:skip] || []).map {|r| /\A#{r}\Z/i}
22
+ @skip_if = opts[:skip_if] if opts[:skip_if]
23
+ @check_only_list = (opts[:check_only] || []).map {|r| /\A#{r}\Z/i}
24
+ @@field = opts[:field] if opts[:field]
25
+ @@header = opts[:header] if opts[:header]
26
+ @@key = opts[:key] if opts[:key]
24
27
 
25
28
  standard_http_methods = %w(POST PUT DELETE PATCH)
26
- check_also = opts[:check_also] || []
29
+ check_also = opts[:check_also] || []
27
30
  @http_methods = (standard_http_methods + check_also).flatten.uniq
28
31
  end
29
32
 
@@ -35,7 +38,8 @@ module Rack
35
38
  req = Rack::Request.new(env)
36
39
  untouchable = skip_checking(req) ||
37
40
  !@http_methods.include?(req.request_method) ||
38
- req.params[self.class.field] == env['rack.session'][self.class.key]
41
+ req.params[self.class.field] == env['rack.session'][self.class.key] ||
42
+ req.env[self.class.rackified_header] == env['rack.session'][self.class.key]
39
43
  if untouchable
40
44
  @app.call(env)
41
45
  else
@@ -52,6 +56,10 @@ module Rack
52
56
  @@field
53
57
  end
54
58
 
59
+ def self.header
60
+ @@header
61
+ end
62
+
55
63
  def self.token(env)
56
64
  env['rack.session'][key] ||= SecureRandom.base64(32)
57
65
  end
@@ -60,21 +68,40 @@ module Rack
60
68
  %Q(<input type="hidden" name="#{field}" value="#{token(env)}" />)
61
69
  end
62
70
 
71
+ def self.metatag(env, options = {})
72
+ name = options.delete(:name) || '_csrf'
73
+ %Q(<meta name="#{name}" content="#{token(env)}" />)
74
+ end
75
+
63
76
  class << self
64
77
  alias_method :csrf_key, :key
65
78
  alias_method :csrf_field, :field
79
+ alias_method :csrf_header, :header
66
80
  alias_method :csrf_token, :token
67
81
  alias_method :csrf_tag, :tag
82
+ alias_method :csrf_metatag, :metatag
68
83
  end
69
84
 
70
85
  protected
71
86
 
87
+ # Returns the custom header's name adapted to current standards.
88
+ def self.rackified_header
89
+ "HTTP_#{@@header.gsub('-','_').upcase}"
90
+ end
91
+
92
+ # Returns +true+ if the given request appears in the <b>skip list</b> or
93
+ # the <b>conditional skipping code</b> return true or, when the <b>check
94
+ # only list</b> is not empty (i.e., we are working in the "reverse mode"
95
+ # triggered by the +check_only+ option), it does not appear in the
96
+ # <b>check only list.</b>
72
97
  def skip_checking request
73
- skip = any? @to_be_skipped, request
74
- allow = any? @to_be_checked, request
75
- skip || (!@to_be_checked.empty? && !allow)
98
+ to_be_skipped = any? @skip_list, request
99
+ to_be_skipped ||= @skip_if && @skip_if.call(request)
100
+ to_be_checked = any? @check_only_list, request
101
+ to_be_skipped || (!@check_only_list.empty? && !to_be_checked)
76
102
  end
77
-
103
+
104
+ # Returns +true+ when the given list "includes" the request.
78
105
  def any? list, request
79
106
  pi = request.path_info.empty? ? '/' : request.path_info
80
107
  list.any? do |route|
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rack_csrf"
8
- s.version = "2.3.0"
8
+ s.version = "2.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Emanuele Vicentini"]
12
- s.date = "2011-10-23"
12
+ s.date = "2012-02-28"
13
13
  s.description = "Anti-CSRF Rack middleware"
14
14
  s.email = "emanuele.vicentini@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.files = [
20
20
  ".rspec",
21
21
  "Changelog.md",
22
+ "Gemfile",
22
23
  "LICENSE.rdoc",
23
24
  "README.rdoc",
24
25
  "Rakefile",
@@ -63,6 +64,7 @@ Gem::Specification.new do |s|
63
64
  "features/inspecting_also_get_requests.feature",
64
65
  "features/raising_exception.feature",
65
66
  "features/setup.feature",
67
+ "features/skip_if_block_passes.feature",
66
68
  "features/skip_some_routes.feature",
67
69
  "features/step_definitions/request_steps.rb",
68
70
  "features/step_definitions/response_steps.rb",
@@ -70,6 +72,7 @@ Gem::Specification.new do |s|
70
72
  "features/support/env.rb",
71
73
  "features/support/fake_session.rb",
72
74
  "features/variation_on_field_name.feature",
75
+ "features/variation_on_header_name.feature",
73
76
  "features/variation_on_key_name.feature",
74
77
  "lib/rack/csrf.rb",
75
78
  "lib/rack/vendor/securerandom.rb",
@@ -79,10 +82,10 @@ Gem::Specification.new do |s|
79
82
  ]
80
83
  s.homepage = "https://github.com/baldowl/rack_csrf"
81
84
  s.licenses = ["MIT"]
82
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Rack::Csrf 2.3.0", "--main", "README.rdoc"]
85
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Rack::Csrf 2.4.0", "--main", "README.rdoc"]
83
86
  s.require_paths = ["lib"]
84
87
  s.rubyforge_project = "rackcsrf"
85
- s.rubygems_version = "1.8.11"
88
+ s.rubygems_version = "1.8.17"
86
89
  s.summary = "Anti-CSRF Rack middleware"
87
90
 
88
91
  if s.respond_to? :specification_version then
@@ -90,23 +93,29 @@ Gem::Specification.new do |s|
90
93
 
91
94
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
92
95
  s.add_runtime_dependency(%q<rack>, [">= 0.9"])
93
- s.add_development_dependency(%q<cucumber>, [">= 0.1.13"])
96
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
97
+ s.add_development_dependency(%q<cucumber>, [">= 1.1.1"])
94
98
  s.add_development_dependency(%q<rack-test>, [">= 0"])
95
99
  s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
96
100
  s.add_development_dependency(%q<rdoc>, [">= 2.4.2"])
101
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
97
102
  else
98
103
  s.add_dependency(%q<rack>, [">= 0.9"])
99
- s.add_dependency(%q<cucumber>, [">= 0.1.13"])
104
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
105
+ s.add_dependency(%q<cucumber>, [">= 1.1.1"])
100
106
  s.add_dependency(%q<rack-test>, [">= 0"])
101
107
  s.add_dependency(%q<rspec>, [">= 2.0.0"])
102
108
  s.add_dependency(%q<rdoc>, [">= 2.4.2"])
109
+ s.add_dependency(%q<jeweler>, [">= 0"])
103
110
  end
104
111
  else
105
112
  s.add_dependency(%q<rack>, [">= 0.9"])
106
- s.add_dependency(%q<cucumber>, [">= 0.1.13"])
113
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
114
+ s.add_dependency(%q<cucumber>, [">= 1.1.1"])
107
115
  s.add_dependency(%q<rack-test>, [">= 0"])
108
116
  s.add_dependency(%q<rspec>, [">= 2.0.0"])
109
117
  s.add_dependency(%q<rdoc>, [">= 2.4.2"])
118
+ s.add_dependency(%q<jeweler>, [">= 0"])
110
119
  end
111
120
  end
112
121