rack_csrf 2.3.0 → 2.4.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.
@@ -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