padrino-csrf 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/.yardopts +9 -0
- data/Gemfile +2 -0
- data/LICENSE +19 -0
- data/README.md +66 -0
- data/Rakefile +7 -0
- data/lib/padrino-csrf/form_helpers.rb +23 -0
- data/lib/padrino-csrf/helpers.rb +54 -0
- data/lib/padrino-csrf/routing.rb +58 -0
- data/lib/padrino-csrf/version.rb +6 -0
- data/lib/padrino-csrf.rb +34 -0
- data/padrino-csrf.gemspec +24 -0
- data/vendor/assets/jquery.unobtrusive.js +76 -0
- metadata +103 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Benjamin Bloch
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
### Overview
|
2
|
+
|
3
|
+
Padrino CSRF is a plugin for the [Padrino](https://github.com/padrino/padrino-framework) web framework which adds [cross-site request forgery](http://en.wikipedia.org/wiki/Cross-site_request_forgery) protection.
|
4
|
+
|
5
|
+
### Setup & Installation
|
6
|
+
|
7
|
+
Include it in your project's `Gemfile` with Bundler:
|
8
|
+
|
9
|
+
``` ruby
|
10
|
+
gem 'padrino-csrf'
|
11
|
+
```
|
12
|
+
|
13
|
+
Modify your `app/app.rb` file to register the plugin:
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
class ExampleApplication < Padrino::Application
|
17
|
+
register Padrino::CSRF
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
### Configuration
|
22
|
+
|
23
|
+
`prevent_request_forgery`
|
24
|
+
When enabled, will automatically verify the CSRF authentication token on all `post`, `put`, and `delete` requests.
|
25
|
+
|
26
|
+
You can of course disable this on a request by request basis:
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
enable :prevent_request_forgery
|
30
|
+
|
31
|
+
post :register do
|
32
|
+
# request is checked
|
33
|
+
end
|
34
|
+
|
35
|
+
post :register, protect: false do
|
36
|
+
# request isn't checked
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Or if you prefer, you can disable it by default, and enable it on a request by request basis:
|
41
|
+
|
42
|
+
``` ruby
|
43
|
+
disable :prevent_request_forgery
|
44
|
+
|
45
|
+
post :register do
|
46
|
+
# request isn't checked
|
47
|
+
end
|
48
|
+
|
49
|
+
post :register, protect: true do
|
50
|
+
# request is checked
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
### Dependencies
|
55
|
+
|
56
|
+
* [Padrino-Core](https://github.com/padrino/padrino-framework) and [Padrino-Helpers](https://github.com/padrino/padrino-framework)
|
57
|
+
* [Ruby](http://www.ruby-lang.org/en) >= 1.9.2
|
58
|
+
|
59
|
+
### TODO
|
60
|
+
|
61
|
+
* Additional documentation
|
62
|
+
* Tests
|
63
|
+
|
64
|
+
### Copyright
|
65
|
+
|
66
|
+
Copyright � 2012 Benjamin Bloch (Cirex). See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Padrino
|
3
|
+
module CSRF
|
4
|
+
module FormHelpers
|
5
|
+
###
|
6
|
+
# Returns a hidden HTML input field with this sessions CSRF token
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
# Generated HTML for hidden CSRF input field
|
10
|
+
#
|
11
|
+
# @since 0.1.0
|
12
|
+
# @api semipublic
|
13
|
+
def token_field_tag
|
14
|
+
input_tag(:hidden, name: csrf_param, value: csrf_token)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
def form_tag(url, options = {}, &block)
|
19
|
+
super(url, options) { token_field_tag + capture_html(&block) }
|
20
|
+
end
|
21
|
+
end # FormHelpers
|
22
|
+
end # CSRF
|
23
|
+
end # Padrino
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Padrino
|
3
|
+
module CSRF
|
4
|
+
module Helpers
|
5
|
+
###
|
6
|
+
# Returns whether or not the CSRF parameter is authentic
|
7
|
+
#
|
8
|
+
# @return [Boolean]
|
9
|
+
# *true* if CSRF is valid, *false* otherwise
|
10
|
+
#
|
11
|
+
# @since 0.1.0
|
12
|
+
# @api semipublic
|
13
|
+
def csrf_valid?
|
14
|
+
csrf_token == params[csrf_param] || csrf_token == request.env['HTTP_X_CSRF_TOKEN']
|
15
|
+
end
|
16
|
+
|
17
|
+
###
|
18
|
+
# Returns HTML meta tags for use with unobtrusive javascript helpers
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
# Generated HTML for CSRF meta tags
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# csrf_meta_tags
|
25
|
+
# # => <meta name="csrf-token" content="71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35">
|
26
|
+
# # => <meta name="csrf-param" content="_csrf_token">
|
27
|
+
#
|
28
|
+
# @since 0.1.0
|
29
|
+
# @api public
|
30
|
+
def csrf_meta_tags
|
31
|
+
[ meta_tag(csrf_token, name: 'csrf-token'),
|
32
|
+
meta_tag(csrf_param, name: 'csrf-param')
|
33
|
+
].join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
###
|
37
|
+
# Returns the CSRF authentication token for the current session
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
# 32 character CSRF token
|
41
|
+
#
|
42
|
+
# @since 0.1.0
|
43
|
+
# @api semipublic
|
44
|
+
def csrf_token
|
45
|
+
session[csrf_param] ||= SecureRandom.hex(32)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def csrf_param
|
50
|
+
:_csrf_token
|
51
|
+
end
|
52
|
+
end # Helpers
|
53
|
+
end # CSRF
|
54
|
+
end # Padrino
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Padrino
|
3
|
+
module CSRF
|
4
|
+
module Routing
|
5
|
+
###
|
6
|
+
# Routing condition which lets you manually toggle CSRF authentication
|
7
|
+
#
|
8
|
+
# @param [Boolean] protect
|
9
|
+
# Should the request be authenticated
|
10
|
+
#
|
11
|
+
# @raise [InvalidToken]
|
12
|
+
# Raised when the CSRF parameter is invalid
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# enable :prevent_request_forgery
|
16
|
+
#
|
17
|
+
# post :register do
|
18
|
+
# # request is checked
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# post :register, protect: false do
|
22
|
+
# # request isn't checked
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# disable :prevent_request_forgery
|
27
|
+
#
|
28
|
+
# post :register do
|
29
|
+
# # request isn't checked
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# post :register, protect: true do
|
33
|
+
# # request is checked
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @since 0.1.0
|
37
|
+
# @api public
|
38
|
+
def protect(protect = false)
|
39
|
+
condition do
|
40
|
+
if protect
|
41
|
+
raise InvalidToken unless csrf_valid?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @private
|
47
|
+
def route(verb, path, options = {}, &block)
|
48
|
+
if REQUEST_BLACKLIST.include?(verb)
|
49
|
+
options[:protect] = settings.prevent_request_forgery if options[:protect] == nil
|
50
|
+
else
|
51
|
+
options.delete(:protect)
|
52
|
+
end
|
53
|
+
|
54
|
+
super(verb, path, options, &block)
|
55
|
+
end
|
56
|
+
end # Routing
|
57
|
+
end # CSRF
|
58
|
+
end # Padrino
|
data/lib/padrino-csrf.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'padrino-core'
|
3
|
+
require 'padrino-helpers'
|
4
|
+
|
5
|
+
FileSet.glob_require('padrino-csrf/**/*.rb', __FILE__)
|
6
|
+
|
7
|
+
module Padrino
|
8
|
+
module CSRF
|
9
|
+
REQUEST_BLACKLIST = %w(POST PUT DELETE)
|
10
|
+
|
11
|
+
class InvalidToken < RuntimeError
|
12
|
+
# @private
|
13
|
+
def http_status
|
14
|
+
403
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# @private
|
20
|
+
def registered(app)
|
21
|
+
app.helpers Helpers
|
22
|
+
app.helpers FormHelpers
|
23
|
+
app.enable :prevent_request_forgery
|
24
|
+
|
25
|
+
app.extend Routing
|
26
|
+
|
27
|
+
if defined?(Padrino::Assets)
|
28
|
+
Padrino::Assets.load_paths << File.expand_path('../../vendor/assets', __FILE__)
|
29
|
+
app.precompile_assets << 'jquery.unobtrusive.js'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end # self
|
33
|
+
end # CSRF
|
34
|
+
end # Padrino
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'padrino-csrf/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'padrino-csrf'
|
7
|
+
s.version = Padrino::CSRF::VERSION
|
8
|
+
s.authors = ['Benjamin Bloch']
|
9
|
+
s.email = ['cirex@gamesol.org']
|
10
|
+
s.homepage = 'https://github.com/Cirex/padrino-csrf'
|
11
|
+
s.description = 'A plugin for the Padrino web framework which adds CSRF protection'
|
12
|
+
s.summary = s.description
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.add_dependency 'padrino-core'
|
20
|
+
s.add_dependency 'padrino-helpers'
|
21
|
+
|
22
|
+
s.add_development_dependency 'minitest'
|
23
|
+
s.add_development_dependency 'webrat'
|
24
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
$('form[data-remote=true]').live('submit', function(e) {
|
2
|
+
e.preventDefault(); e.stopped = true;
|
3
|
+
var element = $(this);
|
4
|
+
var message = element.data('confirm');
|
5
|
+
if (message && !confirm(message)) { return false; }
|
6
|
+
Padrino.sendRequest(element, {
|
7
|
+
dataType: element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType) || 'script',
|
8
|
+
verb: element.data('method') || element.attr('method') || 'post',
|
9
|
+
url: element.attr('action'),
|
10
|
+
params: element.serializeArray()
|
11
|
+
});
|
12
|
+
});
|
13
|
+
|
14
|
+
$('a[data-confirm]').live('click', function(e) {
|
15
|
+
var message = $(this).data('confirm');
|
16
|
+
if (!confirm(message)) { e.preventDefault(); e.stopped = true; }
|
17
|
+
});
|
18
|
+
|
19
|
+
$('a[data-remote=true]').live('click', function(e) {
|
20
|
+
var element = $(this);
|
21
|
+
if (e.stopped) return;
|
22
|
+
e.preventDefault(); e.stopped = true;
|
23
|
+
Padrino.sendRequest(element, {
|
24
|
+
verb: element.data('method') || 'get',
|
25
|
+
url: element.attr('href')
|
26
|
+
});
|
27
|
+
});
|
28
|
+
|
29
|
+
$('a[data-method]:not([data-remote])').live('click', function(e) {
|
30
|
+
if (e.stopped) return;
|
31
|
+
Padrino.sendMethod($(e.target));
|
32
|
+
e.preventDefault(); e.stopped = true;
|
33
|
+
});
|
34
|
+
|
35
|
+
$.ajaxPrefilter(function(options, originalOptions, xhr){ if (!options.crossDomain) { Padrino.CSRFilter(xhr); }});
|
36
|
+
|
37
|
+
var Padrino = {
|
38
|
+
sendRequest : function(element, options) {
|
39
|
+
var verb = options.verb, url = options.url, params = options.params, dataType = options.dataType;
|
40
|
+
var event = element.trigger('ajax:before');
|
41
|
+
if (event.stopped) return false;
|
42
|
+
$.ajax({
|
43
|
+
url: url,
|
44
|
+
type: verb.toUpperCase() || 'POST',
|
45
|
+
data: params || [],
|
46
|
+
dataType: dataType,
|
47
|
+
|
48
|
+
beforeSend: function(request) { element.trigger('ajax:loading', [ request ]); },
|
49
|
+
complete: function(request) { element.trigger('ajax:complete', [ request ]); },
|
50
|
+
success: function(request) { element.trigger('ajax:success', [ request ]); },
|
51
|
+
error: function(request) { element.trigger('ajax:failure', [ request ]); }
|
52
|
+
});
|
53
|
+
element.trigger('ajax:after');
|
54
|
+
},
|
55
|
+
|
56
|
+
sendMethod : function(element) {
|
57
|
+
var verb = element.data('method');
|
58
|
+
var url = element.attr('href');
|
59
|
+
var csrf_token = $('meta[name=csrf-token]').attr('content');
|
60
|
+
var csrf_param = $('meta[name=csrf-param]').attr('content');
|
61
|
+
|
62
|
+
var form = $('<form method="post" action="' + url + '"></form>');
|
63
|
+
form.append('<input type="hidden" name="_method" value="' + verb + '">')
|
64
|
+
if (csrf_param !== undefined && csrf_token !== undefined) {
|
65
|
+
form.append('<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden">');
|
66
|
+
}
|
67
|
+
|
68
|
+
form.hide().appendTo('body');
|
69
|
+
form.submit();
|
70
|
+
},
|
71
|
+
|
72
|
+
CSRFilter: function(xhr) {
|
73
|
+
var token = $('meta[name="csrf-token"]').attr('content');
|
74
|
+
if (token) { xhr.setRequestHeader('X-CSRF-Token', token); }
|
75
|
+
},
|
76
|
+
};
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: padrino-csrf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Benjamin Bloch
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: padrino-core
|
16
|
+
requirement: &18997080 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *18997080
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: padrino-helpers
|
27
|
+
requirement: &18961296 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *18961296
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &18729264 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *18729264
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: webrat
|
49
|
+
requirement: &15937272 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *15937272
|
58
|
+
description: A plugin for the Padrino web framework which adds CSRF protection
|
59
|
+
email:
|
60
|
+
- cirex@gamesol.org
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .yardopts
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- lib/padrino-csrf.rb
|
72
|
+
- lib/padrino-csrf/form_helpers.rb
|
73
|
+
- lib/padrino-csrf/helpers.rb
|
74
|
+
- lib/padrino-csrf/routing.rb
|
75
|
+
- lib/padrino-csrf/version.rb
|
76
|
+
- padrino-csrf.gemspec
|
77
|
+
- vendor/assets/jquery.unobtrusive.js
|
78
|
+
homepage: https://github.com/Cirex/padrino-csrf
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.15
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: A plugin for the Padrino web framework which adds CSRF protection
|
102
|
+
test_files: []
|
103
|
+
has_rdoc:
|