rack_csrf 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|