rack_csrf 1.0.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/LICENSE.rdoc +23 -0
- data/Manifest +25 -0
- data/README.rdoc +69 -0
- data/Rakefile +30 -0
- data/cucumber.yml +1 -0
- data/example/app.rb +12 -0
- data/example/config-with-raise.ru +10 -0
- data/example/config.ru +9 -0
- data/example/views/form.erb +8 -0
- data/example/views/form_not_working.erb +7 -0
- data/example/views/response.erb +5 -0
- data/features/empty_responses.feature +46 -0
- data/features/raising_exception.feature +41 -0
- data/features/setup.feature +24 -0
- data/features/skip_some_routes.feature +33 -0
- data/features/step_definitions/request_steps.rb +45 -0
- data/features/step_definitions/response_steps.rb +21 -0
- data/features/step_definitions/setup_steps.rb +68 -0
- data/features/support/env.rb +4 -0
- data/lib/rack/csrf.rb +57 -0
- data/lib/rack/vendor/securerandom.rb +256 -0
- data/rack_csrf.gemspec +131 -0
- data/spec/csrf_spec.rb +63 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +4 -0
- metadata +134 -0
data/LICENSE.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
= LICENSE
|
2
|
+
|
3
|
+
(The MIT License)
|
4
|
+
|
5
|
+
Copyright (c) 2009 Emanuele Vicentini
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
of this software and associated documentation files (the 'Software'), to deal
|
9
|
+
in the Software without restriction, including without limitation the rights
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
12
|
+
furnished to do so, subject to the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
15
|
+
copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
cucumber.yml
|
2
|
+
example/app.rb
|
3
|
+
example/config-with-raise.ru
|
4
|
+
example/config.ru
|
5
|
+
example/views/form.erb
|
6
|
+
example/views/form_not_working.erb
|
7
|
+
example/views/response.erb
|
8
|
+
features/empty_responses.feature
|
9
|
+
features/raising_exception.feature
|
10
|
+
features/setup.feature
|
11
|
+
features/skip_some_routes.feature
|
12
|
+
features/step_definitions/request_steps.rb
|
13
|
+
features/step_definitions/response_steps.rb
|
14
|
+
features/step_definitions/setup_steps.rb
|
15
|
+
features/support/env.rb
|
16
|
+
lib/rack/csrf.rb
|
17
|
+
lib/rack/vendor/securerandom.rb
|
18
|
+
LICENSE.rdoc
|
19
|
+
Manifest
|
20
|
+
rack_csrf.gemspec
|
21
|
+
Rakefile
|
22
|
+
README.rdoc
|
23
|
+
spec/csrf_spec.rb
|
24
|
+
spec/spec.opts
|
25
|
+
spec/spec_helper.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
= Rack::Csrf
|
2
|
+
|
3
|
+
This is just a small Rack middleware whose only goal is to lessen the hazards
|
4
|
+
posed by CSRF attacks by trying to ensure that all requests of particular
|
5
|
+
types come from the right client, not from a mischievous impersonator.
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
First of all, beyond Rack itself, there is only one prerequisite: you must set
|
10
|
+
up your rack with a session middleware, inserted anywhere before Rack::Csrf.
|
11
|
+
|
12
|
+
Every POST, PUT and DELETE request will be searched for the anti-forging
|
13
|
+
token, randomly generated by Rack::Csrf and stored inside the session. If
|
14
|
+
there's a token and it matches with the stored one, then the request is handed
|
15
|
+
over to the next rack component; if not, Rack::Csrf immediately replies with
|
16
|
+
an empty response.
|
17
|
+
|
18
|
+
I have not tested Rack::Csrf with Rack 0.4.0 or earlier versions, but it could
|
19
|
+
possibly work.
|
20
|
+
|
21
|
+
== Options
|
22
|
+
|
23
|
+
The following options allow you to tweak Rack::Csrf.
|
24
|
+
|
25
|
+
[<tt>:raise</tt>]
|
26
|
+
Set it to true to change the handling of bad request: instead of producing
|
27
|
+
an empty response, Rack::Csrf will raise an exception of class
|
28
|
+
Rack::Csrf::InvalidCsrfToken.
|
29
|
+
|
30
|
+
[<tt>:skip</tt>]
|
31
|
+
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
|
+
|
35
|
+
use Rack::Csrf, :skip => ['POST:/not_checking', 'PUT:/me_too']
|
36
|
+
|
37
|
+
[<tt>:field</tt>]
|
38
|
+
Default field name (see below) is <tt>_csrf</tt>; you can adapt it to
|
39
|
+
specific needs.
|
40
|
+
|
41
|
+
== Helpers
|
42
|
+
|
43
|
+
The following class methods try to ease the insertion of the anti-forging
|
44
|
+
token.
|
45
|
+
|
46
|
+
[<tt>Rack::Csrf.csrf_field</tt>]
|
47
|
+
Returns the name of the field that must be present in the request.
|
48
|
+
|
49
|
+
[<tt>Rack::Csrf.csrf_token(env)</tt>]
|
50
|
+
Given the request's environment, it generates a random token, stuffs it in
|
51
|
+
the session and returns it to the caller or simply retrieves the already
|
52
|
+
stored one.
|
53
|
+
|
54
|
+
[<tt>Rack::Csrf.csrf_tag(env)</tt>]
|
55
|
+
Given the request's environment, it generates a small HTML fragment to
|
56
|
+
insert the token in a standard form like an hidden input field with the
|
57
|
+
right value already entered for you.
|
58
|
+
|
59
|
+
== Working examples
|
60
|
+
|
61
|
+
In the +example+ directory there is a mini Sinatra application with two
|
62
|
+
slightly different rackup files. Beside Rack you only need Sinatra to try
|
63
|
+
them, but Rack::Csrf is not tailored to any particular web framework.
|
64
|
+
|
65
|
+
== Warning! Warning! Warning!
|
66
|
+
|
67
|
+
I cannot stress enough that this middleware is not a bulletproof vest or a
|
68
|
+
panacea for the CSRF plague; it is just an *aid* and by using it you cannot
|
69
|
+
forgo responsibilities for keeping your application as safe as possible.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'cucumber/rake/task'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'echoe'
|
5
|
+
|
6
|
+
Cucumber::Rake::Task.new do |c|
|
7
|
+
c.cucumber_opts = '--profile default'
|
8
|
+
end
|
9
|
+
|
10
|
+
Spec::Rake::SpecTask.new do |t|
|
11
|
+
t.spec_opts = %w(-O spec/spec.opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
Echoe.new('rack_csrf', '1.0.0') do |s|
|
15
|
+
s.author = 'Emanuele Vicentini'
|
16
|
+
s.email = 'emanuele.vicentini@gmail.com'
|
17
|
+
s.summary = 'Anti-CSRF Rack middleware'
|
18
|
+
s.runtime_dependencies = ['rack >=0.9']
|
19
|
+
s.development_dependencies = ['rake >=0.8.2', 'cucumber >=1.1.13', 'rspec', 'echoe']
|
20
|
+
s.need_tar_gz = false
|
21
|
+
s.project = 'rackcsrf'
|
22
|
+
s.gemspec_format = :yaml
|
23
|
+
s.retain_gemspec = true
|
24
|
+
s.rdoc_pattern = /^README|^LICENSE/
|
25
|
+
s.url = 'http://github.com/baldowl/rack_csrf'
|
26
|
+
end
|
27
|
+
|
28
|
+
Rake::Task[:default].clear
|
29
|
+
Rake::Task.tasks.each {|t| t.clear if t.name =~ /test/}
|
30
|
+
task :default => [:features, :spec]
|
data/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: --format pretty -n features
|
data/example/app.rb
ADDED
data/example/config.ru
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<form action="/response" method="post">
|
2
|
+
<h1>Spit your utterance!</h1>
|
3
|
+
<input type="text" name="utterance">
|
4
|
+
<%= Rack::Csrf.csrf_tag(env) %>
|
5
|
+
<p><input type="submit" value="Send!"></p>
|
6
|
+
</form>
|
7
|
+
|
8
|
+
<p>Try also the <a href="/notworking">not working</a> form!</p>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Feature: Handling of the HTTP requests returning an empty response
|
2
|
+
|
3
|
+
Scenario: GET request with CSRF token
|
4
|
+
Given a Rack setup with the anti-CSRF middleware
|
5
|
+
When it receives a GET request with the CSRF token
|
6
|
+
Then it lets it pass untouched
|
7
|
+
|
8
|
+
Scenario: GET request without CSRF token
|
9
|
+
Given a Rack setup with the anti-CSRF middleware
|
10
|
+
When it receives a GET request without the CSRF token
|
11
|
+
Then it lets it pass untouched
|
12
|
+
|
13
|
+
Scenario Outline: Handling request without CSRF token
|
14
|
+
Given a Rack setup with the anti-CSRF middleware
|
15
|
+
When it receives a <method> request without the CSRF token
|
16
|
+
Then it responds with 417
|
17
|
+
And the response body is empty
|
18
|
+
|
19
|
+
Examples:
|
20
|
+
| method |
|
21
|
+
| POST |
|
22
|
+
| PUT |
|
23
|
+
| DELETE |
|
24
|
+
|
25
|
+
Scenario Outline: Handling request with the right CSRF token
|
26
|
+
Given a Rack setup with the anti-CSRF middleware
|
27
|
+
When it receives a <method> request with the right CSRF token
|
28
|
+
Then it lets it pass untouched
|
29
|
+
|
30
|
+
Examples:
|
31
|
+
| method |
|
32
|
+
| POST |
|
33
|
+
| PUT |
|
34
|
+
| DELETE |
|
35
|
+
|
36
|
+
Scenario Outline: Handling request with the wrong CSRF token
|
37
|
+
Given a Rack setup with the anti-CSRF middleware
|
38
|
+
When it receives a <method> request with the wrong CSRF token
|
39
|
+
Then it responds with 417
|
40
|
+
And the response body is empty
|
41
|
+
|
42
|
+
Examples:
|
43
|
+
| method |
|
44
|
+
| POST |
|
45
|
+
| PUT |
|
46
|
+
| DELETE |
|
@@ -0,0 +1,41 @@
|
|
1
|
+
Feature: Handling of the HTTP requests raising an exception
|
2
|
+
|
3
|
+
Scenario: GET request without CSRF token
|
4
|
+
Given a Rack setup with the anti-CSRF middleware and the :raise option
|
5
|
+
When it receives a GET request without the CSRF token
|
6
|
+
Then it lets it pass untouched
|
7
|
+
|
8
|
+
Scenario Outline: Handling request without CSRF token
|
9
|
+
Given a Rack setup with the anti-CSRF middleware and the :raise option
|
10
|
+
When it receives a <method> request without the CSRF token
|
11
|
+
Then there is no response
|
12
|
+
And an exception is climbing up the stack
|
13
|
+
|
14
|
+
Examples:
|
15
|
+
| method |
|
16
|
+
| POST |
|
17
|
+
| PUT |
|
18
|
+
| DELETE |
|
19
|
+
|
20
|
+
Scenario Outline: Handling request with the right CSRF token
|
21
|
+
Given a Rack setup with the anti-CSRF middleware and the :raise option
|
22
|
+
When it receives a <method> request with the right CSRF token
|
23
|
+
Then it lets it pass untouched
|
24
|
+
|
25
|
+
Examples:
|
26
|
+
| method |
|
27
|
+
| POST |
|
28
|
+
| PUT |
|
29
|
+
| DELETE |
|
30
|
+
|
31
|
+
Scenario Outline: Handling request with the wrong CSRF token
|
32
|
+
Given a Rack setup with the anti-CSRF middleware and the :raise option
|
33
|
+
When it receives a <method> request with the wrong CSRF token
|
34
|
+
Then there is no response
|
35
|
+
And an exception is climbing up the stack
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
| method |
|
39
|
+
| POST |
|
40
|
+
| PUT |
|
41
|
+
| DELETE |
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Feature: Setup of the middleware
|
2
|
+
|
3
|
+
Scenario: Simple setup with session support
|
4
|
+
Given a Rack setup with the session middleware
|
5
|
+
When I insert the anti-CSRF middleware
|
6
|
+
Then I get a fully functional rack
|
7
|
+
|
8
|
+
Scenario: Simple setup without session support
|
9
|
+
Given a Rack setup without the session middleware
|
10
|
+
When I insert the anti-CSRF middleware
|
11
|
+
Then I get an error message
|
12
|
+
|
13
|
+
Scenario: Setup with :raise option
|
14
|
+
Given a Rack setup with the session middleware
|
15
|
+
When I insert the anti-CSRF middleware with the :raise option
|
16
|
+
Then I get a fully functional rack
|
17
|
+
|
18
|
+
Scenario: Setup with the :skip option
|
19
|
+
Given a Rack setup with the session middleware
|
20
|
+
When I insert the anti-CSRF middleware with the :skip option
|
21
|
+
| route |
|
22
|
+
| POST:/not_checking |
|
23
|
+
| PUT:/is_wrong |
|
24
|
+
Then I get a fully functional rack
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Skipping the check for some specific routes
|
2
|
+
|
3
|
+
Scenario Outline: Skipping the check for a 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 |
|
8
|
+
When it receives a <method> request for <path> without the CSRF token
|
9
|
+
Then it lets it pass untouched
|
10
|
+
|
11
|
+
Examples:
|
12
|
+
| method | path |
|
13
|
+
| POST | /not_checking |
|
14
|
+
| PUT | /is_wrong |
|
15
|
+
|
16
|
+
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 |
|
21
|
+
When it receives a <method> request for <path> without the CSRF token
|
22
|
+
Then it responds with 417
|
23
|
+
And the response body is empty
|
24
|
+
|
25
|
+
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 |
|
@@ -0,0 +1,45 @@
|
|
1
|
+
When /^it receives a GET request (with|without) the CSRF token$/ do |prep|
|
2
|
+
if prep == 'with'
|
3
|
+
url = "/?#{Rack::Utils.build_query(Rack::Csrf.csrf_field => 'whatever')}"
|
4
|
+
else
|
5
|
+
url = '/'
|
6
|
+
end
|
7
|
+
@response = Rack::MockRequest.new(@app).get(url)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Yes, they're not as DRY as possible, but I think they're more readable than
|
11
|
+
# a single step definition with a few captures and more complex checkings.
|
12
|
+
|
13
|
+
When /^it receives a (POST|PUT|DELETE) request without the CSRF token$/ do |http_method|
|
14
|
+
http_method.downcase!
|
15
|
+
begin
|
16
|
+
@response = Rack::MockRequest.new(@app).send http_method.to_sym, '/'
|
17
|
+
rescue Exception => e
|
18
|
+
@exception = e
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
When /^it receives a (POST|PUT|DELETE) request for (.+) without the CSRF token$/ do |http_method, path|
|
23
|
+
http_method.downcase!
|
24
|
+
begin
|
25
|
+
@response = Rack::MockRequest.new(@app).send http_method.to_sym, path
|
26
|
+
rescue Exception => e
|
27
|
+
@exception = e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
When /^it receives a (POST|PUT|DELETE) request with the right CSRF token$/ do |http_method|
|
32
|
+
http_method.downcase!
|
33
|
+
@response = Rack::MockRequest.new(@app).send http_method.to_sym, '/',
|
34
|
+
:input => "#{Rack::Csrf.csrf_field}=right_token"
|
35
|
+
end
|
36
|
+
|
37
|
+
When /^it receives a (POST|PUT|DELETE) request with the wrong CSRF token$/ do |http_method|
|
38
|
+
http_method.downcase!
|
39
|
+
begin
|
40
|
+
@response = Rack::MockRequest.new(@app).send http_method.to_sym, '/',
|
41
|
+
:input => "#{Rack::Csrf.csrf_field}=whatever"
|
42
|
+
rescue Exception => e
|
43
|
+
@exception = e
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Then /^it lets it pass untouched$/ do
|
2
|
+
@response.should be_ok
|
3
|
+
@response.should =~ /Hello world!/
|
4
|
+
end
|
5
|
+
|
6
|
+
Then /^it responds with 417$/ do
|
7
|
+
@response.status.should == 417
|
8
|
+
end
|
9
|
+
|
10
|
+
Then /^the response body is empty$/ do
|
11
|
+
@response.body.should be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
Then /^there is no response$/ do
|
15
|
+
@response.should be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
Then /^an exception is climbing up the stack$/ do
|
19
|
+
@exception.should_not be_nil
|
20
|
+
@exception.should be_an_instance_of(Rack::Csrf::InvalidCsrfToken)
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
Given /^a Rack setup (with|without) the session middleware$/ do |prep|
|
2
|
+
@rack_builder = Rack::Builder.new
|
3
|
+
@rack_builder.use Rack::Session::Cookie if prep == 'with'
|
4
|
+
end
|
5
|
+
|
6
|
+
class CsrfFaker
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
def call(env)
|
11
|
+
env['rack.session']['rack.csrf'] = 'right_token'
|
12
|
+
@app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Yes, they're not as DRY as possible, but I think they're more readable than
|
17
|
+
# a single step definition with a few captures and more complex checkings.
|
18
|
+
|
19
|
+
Given /^a Rack setup with the anti\-CSRF middleware$/ do
|
20
|
+
Given 'a Rack setup with the session middleware'
|
21
|
+
@rack_builder.use CsrfFaker
|
22
|
+
When 'I insert the anti-CSRF middleware'
|
23
|
+
end
|
24
|
+
|
25
|
+
Given /^a Rack setup with the anti\-CSRF middleware and the :raise option$/ do
|
26
|
+
Given 'a Rack setup with the session middleware'
|
27
|
+
@rack_builder.use CsrfFaker
|
28
|
+
When 'I insert the anti-CSRF middleware with the :raise option'
|
29
|
+
end
|
30
|
+
|
31
|
+
Given /^a Rack setup with the anti\-CSRF middleware and the :skip option$/ do |table|
|
32
|
+
Given 'a Rack setup with the session middleware'
|
33
|
+
@rack_builder.use CsrfFaker
|
34
|
+
When 'I insert the anti-CSRF middleware with the :skip option', table
|
35
|
+
end
|
36
|
+
|
37
|
+
# Yes, they're not as DRY as possible, but I think they're more readable than
|
38
|
+
# a single step definition with a few captures and more complex checkings.
|
39
|
+
|
40
|
+
When /^I insert the anti\-CSRF middleware$/ do
|
41
|
+
@rack_builder.use Rack::Lint
|
42
|
+
@rack_builder.use Rack::Csrf
|
43
|
+
@rack_builder.run(lambda {|env| Rack::Response.new('Hello world!').finish})
|
44
|
+
@app = @rack_builder.to_app
|
45
|
+
end
|
46
|
+
|
47
|
+
When /^I insert the anti\-CSRF middleware with the :raise option$/ do
|
48
|
+
@rack_builder.use Rack::Lint
|
49
|
+
@rack_builder.use Rack::Csrf, :raise => true
|
50
|
+
@rack_builder.run(lambda {|env| Rack::Response.new('Hello world!').finish})
|
51
|
+
@app = @rack_builder.to_app
|
52
|
+
end
|
53
|
+
|
54
|
+
When /^I insert the anti\-CSRF middleware with the :skip option$/ do |table|
|
55
|
+
skippable = table.hashes.collect {|t| t.values}.flatten
|
56
|
+
@rack_builder.use Rack::Lint
|
57
|
+
@rack_builder.use Rack::Csrf, :skip => skippable
|
58
|
+
@rack_builder.run(lambda {|env| Rack::Response.new('Hello world!').finish})
|
59
|
+
@app = @rack_builder.to_app
|
60
|
+
end
|
61
|
+
|
62
|
+
Then /^I get a fully functional rack$/ do
|
63
|
+
lambda {Rack::MockRequest.new(@app).get('/')}.should_not raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
Then /^I get an error message$/ do
|
67
|
+
lambda {Rack::MockRequest.new(@app).get('/')}.should raise_error(Rack::Csrf::SessionUnavailable, 'Rack::Csrf depends on session middleware')
|
68
|
+
end
|
data/lib/rack/csrf.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rack'
|
2
|
+
begin
|
3
|
+
require 'securerandom'
|
4
|
+
rescue LoadError
|
5
|
+
require File.dirname(__FILE__) + '/vendor/securerandom'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Rack
|
9
|
+
class Csrf
|
10
|
+
class SessionUnavailable < StandardError; end
|
11
|
+
class InvalidCsrfToken < StandardError; end
|
12
|
+
|
13
|
+
@@field = '_csrf'
|
14
|
+
|
15
|
+
def initialize(app, opts = {})
|
16
|
+
@app = app
|
17
|
+
@raisable = opts[:raise] || false
|
18
|
+
@skippable = opts[:skip] || []
|
19
|
+
@skippable.map {|s| s.downcase!}
|
20
|
+
@@field = opts[:field] if opts[:field]
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
unless env['rack.session']
|
25
|
+
raise SessionUnavailable.new('Rack::Csrf depends on session middleware')
|
26
|
+
end
|
27
|
+
req = Rack::Request.new(env)
|
28
|
+
untouchable = !%w(POST PUT DELETE).include?(req.request_method) ||
|
29
|
+
req.POST[self.class.csrf_field] == env['rack.session']['rack.csrf'] ||
|
30
|
+
skip_checking(req)
|
31
|
+
if untouchable
|
32
|
+
@app.call(env)
|
33
|
+
else
|
34
|
+
raise InvalidCsrfToken if @raisable
|
35
|
+
[417, {'Content-Type' => 'text/html', 'Content-Length' => '0'}, []]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.csrf_field
|
40
|
+
@@field
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.csrf_token(env)
|
44
|
+
env['rack.session']['rack.csrf'] ||= SecureRandom.base64(32)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.csrf_tag(env)
|
48
|
+
%Q(<input type="hidden" name="#{csrf_field}" value="#{csrf_token(env)}" />)
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def skip_checking request
|
54
|
+
@skippable.include?(request.request_method.downcase + ':' + request.path_info)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# Library taken from Ruby 1.9 SVN repository on 2009-04-15T10:25Z
|
2
|
+
# For copyright and license see http://www.ruby-lang.org
|
3
|
+
|
4
|
+
# = Secure random number generator interface.
|
5
|
+
#
|
6
|
+
# This library is an interface for secure random number generator which is
|
7
|
+
# suitable for generating session key in HTTP cookies, etc.
|
8
|
+
#
|
9
|
+
# It supports following secure random number generators.
|
10
|
+
#
|
11
|
+
# * openssl
|
12
|
+
# * /dev/urandom
|
13
|
+
# * Win32
|
14
|
+
#
|
15
|
+
# == Example
|
16
|
+
#
|
17
|
+
# # random hexadecimal string.
|
18
|
+
# p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
|
19
|
+
# p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
|
20
|
+
# p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
|
21
|
+
# p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
|
22
|
+
# p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
|
23
|
+
# ...
|
24
|
+
#
|
25
|
+
# # random base64 string.
|
26
|
+
# p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
|
27
|
+
# p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
|
28
|
+
# p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
|
29
|
+
# p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
|
30
|
+
# p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
|
31
|
+
# p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
|
32
|
+
# ...
|
33
|
+
#
|
34
|
+
# # random binary string.
|
35
|
+
# p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
|
36
|
+
# p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
|
37
|
+
# ...
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'openssl'
|
41
|
+
rescue LoadError
|
42
|
+
end
|
43
|
+
|
44
|
+
module SecureRandom
|
45
|
+
# SecureRandom.random_bytes generates a random binary string.
|
46
|
+
#
|
47
|
+
# The argument n specifies the length of the result string.
|
48
|
+
#
|
49
|
+
# If n is not specified, 16 is assumed.
|
50
|
+
# It may be larger in future.
|
51
|
+
#
|
52
|
+
# The result may contain any byte: "\x00" - "\xff".
|
53
|
+
#
|
54
|
+
# p SecureRandom.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
|
55
|
+
# p SecureRandom.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
|
56
|
+
#
|
57
|
+
# If secure random number generator is not available,
|
58
|
+
# NotImplementedError is raised.
|
59
|
+
def self.random_bytes(n=nil)
|
60
|
+
n ||= 16
|
61
|
+
|
62
|
+
if defined? OpenSSL::Random
|
63
|
+
return OpenSSL::Random.random_bytes(n)
|
64
|
+
end
|
65
|
+
|
66
|
+
if !defined?(@has_urandom) || @has_urandom
|
67
|
+
flags = File::RDONLY
|
68
|
+
flags |= File::NONBLOCK if defined? File::NONBLOCK
|
69
|
+
flags |= File::NOCTTY if defined? File::NOCTTY
|
70
|
+
flags |= File::NOFOLLOW if defined? File::NOFOLLOW
|
71
|
+
begin
|
72
|
+
File.open("/dev/urandom", flags) {|f|
|
73
|
+
unless f.stat.chardev?
|
74
|
+
raise Errno::ENOENT
|
75
|
+
end
|
76
|
+
@has_urandom = true
|
77
|
+
ret = f.readpartial(n)
|
78
|
+
if ret.length != n
|
79
|
+
raise NotImplementedError, "Unexpected partial read from random device"
|
80
|
+
end
|
81
|
+
return ret
|
82
|
+
}
|
83
|
+
rescue Errno::ENOENT
|
84
|
+
@has_urandom = false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if !defined?(@has_win32)
|
89
|
+
begin
|
90
|
+
require 'Win32API'
|
91
|
+
|
92
|
+
crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
|
93
|
+
@crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
|
94
|
+
|
95
|
+
hProvStr = " " * 4
|
96
|
+
prov_rsa_full = 1
|
97
|
+
crypt_verifycontext = 0xF0000000
|
98
|
+
|
99
|
+
if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
|
100
|
+
raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
|
101
|
+
end
|
102
|
+
@hProv, = hProvStr.unpack('L')
|
103
|
+
|
104
|
+
@has_win32 = true
|
105
|
+
rescue LoadError
|
106
|
+
@has_win32 = false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
if @has_win32
|
110
|
+
bytes = " " * n
|
111
|
+
if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
|
112
|
+
raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
|
113
|
+
end
|
114
|
+
return bytes
|
115
|
+
end
|
116
|
+
|
117
|
+
raise NotImplementedError, "No random device"
|
118
|
+
end
|
119
|
+
|
120
|
+
# SecureRandom.hex generates a random hex string.
|
121
|
+
#
|
122
|
+
# The argument n specifies the length of the random length.
|
123
|
+
# The length of the result string is twice of n.
|
124
|
+
#
|
125
|
+
# If n is not specified, 16 is assumed.
|
126
|
+
# It may be larger in future.
|
127
|
+
#
|
128
|
+
# The result may contain 0-9 and a-f.
|
129
|
+
#
|
130
|
+
# p SecureRandom.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
|
131
|
+
# p SecureRandom.hex #=> "91dc3bfb4de5b11d029d376634589b61"
|
132
|
+
#
|
133
|
+
# If secure random number generator is not available,
|
134
|
+
# NotImplementedError is raised.
|
135
|
+
def self.hex(n=nil)
|
136
|
+
random_bytes(n).unpack("H*")[0]
|
137
|
+
end
|
138
|
+
|
139
|
+
# SecureRandom.base64 generates a random base64 string.
|
140
|
+
#
|
141
|
+
# The argument n specifies the length of the random length.
|
142
|
+
# The length of the result string is about 4/3 of n.
|
143
|
+
#
|
144
|
+
# If n is not specified, 16 is assumed.
|
145
|
+
# It may be larger in future.
|
146
|
+
#
|
147
|
+
# The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
|
148
|
+
#
|
149
|
+
# p SecureRandom.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
|
150
|
+
# p SecureRandom.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
|
151
|
+
#
|
152
|
+
# If secure random number generator is not available,
|
153
|
+
# NotImplementedError is raised.
|
154
|
+
#
|
155
|
+
# See RFC 3548 for base64.
|
156
|
+
def self.base64(n=nil)
|
157
|
+
[random_bytes(n)].pack("m*").delete("\n")
|
158
|
+
end
|
159
|
+
|
160
|
+
# SecureRandom.urlsafe_base64 generates a random URL-safe base64 string.
|
161
|
+
#
|
162
|
+
# The argument _n_ specifies the length of the random length.
|
163
|
+
# The length of the result string is about 4/3 of _n_.
|
164
|
+
#
|
165
|
+
# If _n_ is not specified, 16 is assumed.
|
166
|
+
# It may be larger in future.
|
167
|
+
#
|
168
|
+
# The boolean argument _padding_ specifies the padding.
|
169
|
+
# If it is false or nil, padding is not generated.
|
170
|
+
# Otherwise padding is generated.
|
171
|
+
# By default, padding is not generated because "=" may be used as a URL delimiter.
|
172
|
+
#
|
173
|
+
# The result may contain A-Z, a-z, 0-9, "-" and "_".
|
174
|
+
# "=" is also used if _padding_ is true.
|
175
|
+
#
|
176
|
+
# p SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
|
177
|
+
# p SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
|
178
|
+
#
|
179
|
+
# p SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
|
180
|
+
# p SecureRandom.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
|
181
|
+
#
|
182
|
+
# If secure random number generator is not available,
|
183
|
+
# NotImplementedError is raised.
|
184
|
+
#
|
185
|
+
# See RFC 3548 for URL-safe base64.
|
186
|
+
def self.urlsafe_base64(n=nil, padding=false)
|
187
|
+
s = [random_bytes(n)].pack("m*")
|
188
|
+
s.delete!("\n")
|
189
|
+
s.tr!("+/", "-_")
|
190
|
+
s.delete!("=") if !padding
|
191
|
+
s
|
192
|
+
end
|
193
|
+
|
194
|
+
# SecureRandom.random_number generates a random number.
|
195
|
+
#
|
196
|
+
# If an positive integer is given as n,
|
197
|
+
# SecureRandom.random_number returns an integer:
|
198
|
+
# 0 <= SecureRandom.random_number(n) < n.
|
199
|
+
#
|
200
|
+
# p SecureRandom.random_number(100) #=> 15
|
201
|
+
# p SecureRandom.random_number(100) #=> 88
|
202
|
+
#
|
203
|
+
# If 0 is given or an argument is not given,
|
204
|
+
# SecureRandom.random_number returns an float:
|
205
|
+
# 0.0 <= SecureRandom.random_number() < 1.0.
|
206
|
+
#
|
207
|
+
# p SecureRandom.random_number #=> 0.596506046187744
|
208
|
+
# p SecureRandom.random_number #=> 0.350621695741409
|
209
|
+
#
|
210
|
+
def self.random_number(n=0)
|
211
|
+
if 0 < n
|
212
|
+
hex = n.to_s(16)
|
213
|
+
hex = '0' + hex if (hex.length & 1) == 1
|
214
|
+
bin = [hex].pack("H*")
|
215
|
+
mask = bin[0].ord
|
216
|
+
mask |= mask >> 1
|
217
|
+
mask |= mask >> 2
|
218
|
+
mask |= mask >> 4
|
219
|
+
begin
|
220
|
+
rnd = SecureRandom.random_bytes(bin.length)
|
221
|
+
rnd[0] = (rnd[0].ord & mask).chr
|
222
|
+
end until rnd < bin
|
223
|
+
rnd.unpack("H*")[0].hex
|
224
|
+
else
|
225
|
+
# assumption: Float::MANT_DIG <= 64
|
226
|
+
i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
|
227
|
+
Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# SecureRandom.uuid generates a v4 random UUID (Universally Unique IDentifier).
|
232
|
+
#
|
233
|
+
# p SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
|
234
|
+
# p SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
|
235
|
+
#
|
236
|
+
# See RFC 4122 for UUID.
|
237
|
+
def self.uuid
|
238
|
+
ary = self.random_bytes(16).unpack("NnnnnN")
|
239
|
+
ary[2] = (ary[2] & 0x0fff) | 0x4000
|
240
|
+
ary[3] = (ary[3] & 0x3fff) | 0x8000
|
241
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
242
|
+
end
|
243
|
+
|
244
|
+
# Following code is based on David Garamond's GUID library for Ruby.
|
245
|
+
def self.lastWin32ErrorMessage # :nodoc:
|
246
|
+
get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
|
247
|
+
format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
|
248
|
+
format_message_ignore_inserts = 0x00000200
|
249
|
+
format_message_from_system = 0x00001000
|
250
|
+
|
251
|
+
code = get_last_error.call
|
252
|
+
msg = "\0" * 1024
|
253
|
+
len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
|
254
|
+
msg[0, len].tr("\r", '').chomp
|
255
|
+
end
|
256
|
+
end
|
data/rack_csrf.gemspec
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack_csrf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emanuele Vicentini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
|
11
|
+
date: 2009-04-22 00:00:00 +02:00
|
12
|
+
default_executable:
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
type: :runtime
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0.9"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: rake
|
26
|
+
type: :development
|
27
|
+
version_requirement:
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.8.2
|
33
|
+
version:
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: cucumber
|
36
|
+
type: :development
|
37
|
+
version_requirement:
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.1.13
|
43
|
+
version:
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rspec
|
46
|
+
type: :development
|
47
|
+
version_requirement:
|
48
|
+
version_requirements: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: echoe
|
56
|
+
type: :development
|
57
|
+
version_requirement:
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
description: Anti-CSRF Rack middleware
|
65
|
+
email: emanuele.vicentini@gmail.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files:
|
71
|
+
- LICENSE.rdoc
|
72
|
+
- README.rdoc
|
73
|
+
files:
|
74
|
+
- cucumber.yml
|
75
|
+
- example/app.rb
|
76
|
+
- example/config-with-raise.ru
|
77
|
+
- example/config.ru
|
78
|
+
- example/views/form.erb
|
79
|
+
- example/views/form_not_working.erb
|
80
|
+
- example/views/response.erb
|
81
|
+
- features/empty_responses.feature
|
82
|
+
- features/raising_exception.feature
|
83
|
+
- features/setup.feature
|
84
|
+
- features/skip_some_routes.feature
|
85
|
+
- features/step_definitions/request_steps.rb
|
86
|
+
- features/step_definitions/response_steps.rb
|
87
|
+
- features/step_definitions/setup_steps.rb
|
88
|
+
- features/support/env.rb
|
89
|
+
- lib/rack/csrf.rb
|
90
|
+
- lib/rack/vendor/securerandom.rb
|
91
|
+
- LICENSE.rdoc
|
92
|
+
- Manifest
|
93
|
+
- rack_csrf.gemspec
|
94
|
+
- Rakefile
|
95
|
+
- README.rdoc
|
96
|
+
- spec/csrf_spec.rb
|
97
|
+
- spec/spec.opts
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
has_rdoc: true
|
100
|
+
homepage: http://github.com/baldowl/rack_csrf
|
101
|
+
licenses: []
|
102
|
+
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options:
|
105
|
+
- --line-numbers
|
106
|
+
- --inline-source
|
107
|
+
- --title
|
108
|
+
- Rack_csrf
|
109
|
+
- --main
|
110
|
+
- README.rdoc
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: "0"
|
118
|
+
version:
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: "1.2"
|
124
|
+
version:
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project: rackcsrf
|
128
|
+
rubygems_version: 1.3.2
|
129
|
+
specification_version: 3
|
130
|
+
summary: Anti-CSRF Rack middleware
|
131
|
+
test_files: []
|
data/spec/csrf_spec.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Rack::Csrf do
|
4
|
+
describe '#csrf_field' do
|
5
|
+
it "should be '_csrf'" do
|
6
|
+
Rack::Csrf.csrf_field.should == '_csrf'
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be the value of :field option" do
|
10
|
+
fakeapp = [200, {}, []]
|
11
|
+
Rack::Csrf.new fakeapp, :field => 'whatever'
|
12
|
+
Rack::Csrf.csrf_field.should == 'whatever'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#csrf_token' do
|
17
|
+
before do
|
18
|
+
@env = {'rack.session' => {}}
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should be at least 32 characters long' do
|
22
|
+
Rack::Csrf.csrf_token(@env).length.should >= 32
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should store the token inside the session if it is not already there' do
|
26
|
+
@env['rack.session'].should be_empty
|
27
|
+
Rack::Csrf.csrf_token(@env)
|
28
|
+
@env['rack.session'].should_not be_empty
|
29
|
+
@env['rack.session']['rack.csrf'].should_not be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should get the token from the session if it is already there' do
|
33
|
+
@env['rack.session'].should be_empty
|
34
|
+
csrf_token = Rack::Csrf.csrf_token(@env)
|
35
|
+
csrf_token.should == @env['rack.session']['rack.csrf']
|
36
|
+
csrf_token.should == Rack::Csrf.csrf_token(@env)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#csrf_tag' do
|
41
|
+
before do
|
42
|
+
@env = {'rack.session' => {}}
|
43
|
+
@tag = Rack::Csrf.csrf_tag(@env)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should be an input field' do
|
47
|
+
@tag.should =~ /^<input/
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should be an hidden input field' do
|
51
|
+
@tag.should =~ /type="hidden"/
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should have the csrf_field's name" do
|
55
|
+
@tag.should =~ /name="#{Rack::Csrf.csrf_field}"/
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have the csrf_token's output" do
|
59
|
+
quoted_value = Regexp.quote %Q(value="#{Rack::Csrf.csrf_token(@env)}")
|
60
|
+
@tag.should =~ /#{quoted_value}/
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack_csrf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emanuele Vicentini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-22 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0.9"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.8.2
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: cucumber
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.1.13
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rspec
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: echoe
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
description: Anti-CSRF Rack middleware
|
66
|
+
email: emanuele.vicentini@gmail.com
|
67
|
+
executables: []
|
68
|
+
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files:
|
72
|
+
- LICENSE.rdoc
|
73
|
+
- README.rdoc
|
74
|
+
files:
|
75
|
+
- cucumber.yml
|
76
|
+
- example/app.rb
|
77
|
+
- example/config-with-raise.ru
|
78
|
+
- example/config.ru
|
79
|
+
- example/views/form.erb
|
80
|
+
- example/views/form_not_working.erb
|
81
|
+
- example/views/response.erb
|
82
|
+
- features/empty_responses.feature
|
83
|
+
- features/raising_exception.feature
|
84
|
+
- features/setup.feature
|
85
|
+
- features/skip_some_routes.feature
|
86
|
+
- features/step_definitions/request_steps.rb
|
87
|
+
- features/step_definitions/response_steps.rb
|
88
|
+
- features/step_definitions/setup_steps.rb
|
89
|
+
- features/support/env.rb
|
90
|
+
- lib/rack/csrf.rb
|
91
|
+
- lib/rack/vendor/securerandom.rb
|
92
|
+
- LICENSE.rdoc
|
93
|
+
- Manifest
|
94
|
+
- rack_csrf.gemspec
|
95
|
+
- Rakefile
|
96
|
+
- README.rdoc
|
97
|
+
- spec/csrf_spec.rb
|
98
|
+
- spec/spec.opts
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
has_rdoc: true
|
101
|
+
homepage: http://github.com/baldowl/rack_csrf
|
102
|
+
licenses: []
|
103
|
+
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options:
|
106
|
+
- --line-numbers
|
107
|
+
- --inline-source
|
108
|
+
- --title
|
109
|
+
- Rack_csrf
|
110
|
+
- --main
|
111
|
+
- README.rdoc
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: "0"
|
119
|
+
version:
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: "1.2"
|
125
|
+
version:
|
126
|
+
requirements: []
|
127
|
+
|
128
|
+
rubyforge_project: rackcsrf
|
129
|
+
rubygems_version: 1.3.2
|
130
|
+
signing_key:
|
131
|
+
specification_version: 3
|
132
|
+
summary: Anti-CSRF Rack middleware
|
133
|
+
test_files: []
|
134
|
+
|