bchiu-merb_forgery_protection 0.0.1

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 ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 YOUR NAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,71 @@
1
+ = merb_forgery_protection
2
+
3
+ Merb plugin that provides forgery protection against css attacks.
4
+
5
+ Protect a controller's actions from CSRF attacks by ensuring that all forms
6
+ are coming from the current web application, not a forged link from another
7
+ site. This is done by embedding a token based on the session (which an
8
+ attacker wouldn't know) in all forms and Ajax requests generated by Merb
9
+ and then verifying the authenticity of that token in the controller. Only
10
+ HTML/JavaScript requests are checked, so this will not protect your XML API
11
+ (presumably you'll have a different authentication scheme there anyway).
12
+ Also, GET requests are not protected as these should be indempotent anyway.
13
+
14
+ You turn this on with the #protect_from_forgery method, which will perform
15
+ the check and raise a InvalidAuthenticityToken exception if the token doesn't
16
+ match what was expected. And it will add an authenticity_token parameter to
17
+ all forms that are automatically generated by Merb. You can customize the
18
+ error message given through public/422.html.
19
+
20
+ Learn more about CSRF (Cross-Site Request Forgery) attacks:
21
+
22
+ * http://isc.sans.org/diary.html?storyid=1750
23
+ * http://en.wikipedia.org/wiki/Cross-site_request_forgery
24
+
25
+ Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security
26
+ blanket for your merb app. There are a few guidelines you should follow:
27
+
28
+ * Keep your GET requests safe and idempotent. More reading material:
29
+ * http://www.xml.com/pub/a/2002/04/24/deviant.html
30
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
31
+ * Make sure the session cookies that Rails creates are non-persistent.
32
+ Check in Firefox and look for "Expires: at end of session"
33
+
34
+ If you need to construct a request yourself, but still want to take advantage
35
+ of forgery protection, you can grab the authenticity_token using the
36
+ authenticity_token helper method and make it part of the parameters yourself.
37
+
38
+ == Installation
39
+
40
+ git clone git://github.com/bchiu/merb_forgery_protection.git
41
+ cd merb_forgery_protection
42
+ rake install
43
+
44
+ == Example
45
+
46
+ class Foo < Application
47
+ # uses the cookie session store (then you don't need a separate :secret)
48
+ protect_from_forgery :exclude => :index
49
+
50
+ # uses one of the other session stores that uses a session_id value.
51
+ protect_from_forgery :secret => 'my-little-pony', :exclude => :index
52
+
53
+ # you can disable csrf protection on controller-by-controller basis:
54
+ protect_from_forgery :enable => false
55
+ end
56
+
57
+ == Configuration
58
+
59
+ To disable forgery protection globally put this in your init.rb:
60
+ Merb::Plugins.config[:forgery_protection] = { :enable => false }
61
+
62
+ === Global Options:
63
+ :secret - salt used to generate the token (default :session_secret_key)
64
+ :enable - enable/disable protection for all controllers (default true)
65
+ :digest - message digest used for hashing (default 'SHA1')
66
+ :token_name - form field name for token (default :authenticity_token)
67
+
68
+ === Controller Options:
69
+ :only/:exclude - set which controller actions are protected from forgery
70
+ :enable - enable/disable protection for this controller (default true)
71
+ :secret - salt used to generate the token (default :session_secret_key)
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'merb_rake_helper'
6
+
7
+ PLUGIN = "merb_forgery_protection"
8
+ NAME = "merb_forgery_protection"
9
+ GEM_VERSION = "0.0.1"
10
+ AUTHOR = "Ben Chiu"
11
+ EMAIL = "bchiu@yahoo.com"
12
+ HOMEPAGE = "http://github.com/bchiu/merb_forgery_protection"
13
+ SUMMARY = "Merb plugin that provides forgery protection against css attacks"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = NAME
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.add_dependency('merb', '>= 0.9.0')
27
+ s.require_path = 'lib'
28
+ s.autorequire = PLUGIN
29
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
30
+ end
31
+
32
+ Rake::GemPackageTask.new(spec) do |pkg|
33
+ pkg.gem_spec = spec
34
+ end
35
+
36
+ desc "install the plugin locally"
37
+ task :install => [:package] do
38
+ sh %{#{sudo} #{gemx} install pkg/#{NAME}-#{GEM_VERSION} --local --no-update-sources}
39
+ end
40
+
41
+ desc "create a gemspec file"
42
+ task :make_spec do
43
+ File.open("#{NAME}.gemspec", "w") do |file|
44
+ file.puts spec.to_ruby
45
+ end
46
+ end
47
+
48
+ namespace :jruby do
49
+
50
+ desc "Run :package and install the resulting .gem with jruby"
51
+ task :install => :package do
52
+ sh %{#{sudo} jruby -S gem install pkg/#{NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
53
+ end
54
+
55
+ end
data/TODO ADDED
@@ -0,0 +1 @@
1
+ TODO:
@@ -0,0 +1,23 @@
1
+ if defined?(Merb::Plugins)
2
+ require 'merb_forgery_protection/forgery_protection'
3
+ require 'merb_forgery_protection/form_helpers'
4
+
5
+ DEFAULT_CONFIG = {
6
+ :secret => Merb::Config[:session_secret_key],
7
+ :enable => true,
8
+ :digest => 'SHA1',
9
+ :token_name => :authenticity_token
10
+ }
11
+
12
+ Merb::Plugins.config[:merb_forgery_protection] = DEFAULT_CONFIG.update(
13
+ Merb::Plugins.config[:merb_forgery_protection]||{}
14
+ )
15
+
16
+ Merb::BootLoader.before_app_loads do
17
+ end
18
+
19
+ Merb::BootLoader.after_app_loads do
20
+ end
21
+
22
+ Merb::Plugins.add_rakefiles "merb_forgery_protection/merbtasks"
23
+ end
@@ -0,0 +1,65 @@
1
+ module Merb::ControllerExceptions
2
+ class InvalidAuthenticityToken < ClientError
3
+ self.status = 401
4
+ end
5
+ end
6
+
7
+ class Merb::Controller
8
+ attr_accessor :protection_options
9
+ @protection_options = {}
10
+
11
+ def self.protect_from_forgery(opts = {})
12
+ with = {}
13
+ [:secret,:enable].each {|k| with[k] = opts.delete(k) if opts.key?(k) }
14
+ before :verify_token, opts.merge(:with => [with])
15
+ end
16
+
17
+ # controller instance methods
18
+
19
+ def authenticity_token
20
+ token_name = @protection_options[:token_name].to_s
21
+ if @protection_options[:secret]
22
+ { :name => token_name, :value => token_from_session_id }.to_mash
23
+ elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
24
+ { :name => token_name, :value => token_from_cookie_session }.to_mash
25
+ elsif session.nil?
26
+ raise Merb::ControllerExceptions::InvalidAuthenticityToken, "Forgery Protection requires a valid session. Please disable it or use a valid session."
27
+ else
28
+ raise Merb::ControllerExceptions::InvalidAuthenticityToken, "No :secret given. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
29
+ end
30
+ end
31
+
32
+ def protect_against_forgery?
33
+ @protection_options && @protection_options[:enable]
34
+ end
35
+
36
+ private
37
+
38
+ def verify_token(opts = {})
39
+ @protection_options ||= Merb::Plugins.config[:merb_forgery_protection].update(opts)
40
+ verified_request? || raise(Merb::ControllerExceptions::InvalidAuthenticityToken)
41
+ end
42
+
43
+ def verified_request?
44
+ request.method == :get ||
45
+ !@protection_options[:enable] ||
46
+ ![:html,:js].include?(content_type) ||
47
+ params[@protection_options[:token_name]] == authenticity_token
48
+ end
49
+
50
+ def token_from_session_id
51
+ if @protection_options[:secret].respond_to?(:call)
52
+ key = @protection_options[:secret].call(session)
53
+ else
54
+ key = @protection_options[:secret]
55
+ end
56
+ digest = @protection_options[:digest]
57
+ session_id = cookies[_session_id_key]
58
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session_id)
59
+ end
60
+
61
+ def token_from_cookie_session
62
+ session[:csrf_id] ||= rand_uuid
63
+ session.dbman.generate_digest(session[:csrf_id])
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ module Merb::Helpers::Form
2
+
3
+ # Generates a form tag, which accepts a block that is not directly based on resource attributes
4
+ #
5
+ # <% form_tag(:action => url(:controller => "foo", :action => "bar", :id => 1)) do %>
6
+ # <%= text_field :name => 'first_name', :label => 'First Name' %>
7
+ # <%= submit_button 'Create' %>
8
+ # <% end %>
9
+ #
10
+ # The HTML generated for this would be:
11
+ #
12
+ # <form action="/foo/bar/1" method="post">
13
+ # <label for="first_name">First Name</label><input id="first_name" name="first_name" size="30" type="text" />
14
+ # <input name="commit" type="submit" value="Create" />
15
+ # </form>
16
+ def form_tag(attrs = {}, &block)
17
+ set_multipart_attribute!(attrs)
18
+ fake_form_method = set_form_method(attrs)
19
+ concat(open_tag("form", attrs), block.binding)
20
+ concat(generate_fake_form_method(fake_form_method), block.binding) if fake_form_method
21
+ concat(capture(&block), block.binding)
22
+ concat(token_field, block.binding) if attrs[:method] == :post
23
+ concat("</form>", block.binding)
24
+ end
25
+
26
+ # Generates a resource specific form tag which accepts a block, this also provides automatic resource routing.
27
+ # <% form_for :person, :action => url(:people) do %>
28
+ # <%= text_control :first_name, :label => 'First Name' %>
29
+ # <%= text_control :last_name, :label => 'Last Name' %>
30
+ # <%= submit_button 'Create' %>
31
+ # <% end %>
32
+ #
33
+ # The HTML generated for this would be:
34
+ #
35
+ # <form action="/people/create" method="post">
36
+ # <label for="person[first_name]">First Name</label><input id="person_first_name" name="person[first_name]" size="30" type="text" />
37
+ # <label for="person[last_name]">Last Name</label><input id="person_last_name" name="person[last_name]" size="30" type="text" />
38
+ # <input name="commit" type="submit" value="Create" />
39
+ # </form>
40
+ def form_for(obj, attrs={}, &block)
41
+ set_multipart_attribute!(attrs)
42
+ obj = obj_from_ivar_or_sym(obj)
43
+ fake_form_method = set_form_method(attrs, obj)
44
+ concat(open_tag("form", attrs), block.binding)
45
+ concat(generate_fake_form_method(fake_form_method), block.binding) if fake_form_method
46
+ fields_for(obj, attrs, &block)
47
+ concat(token_field, block.binding) if attrs[:method] == :post
48
+ concat("</form>", block.binding)
49
+ end
50
+
51
+ private
52
+
53
+ def token_field
54
+ return '' if !protect_against_forgery?
55
+ hidden_field(:name => authenticity_token[:name], :value => authenticity_token[:value])
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ namespace :merb_forgery_protection do
2
+ desc "Do something for merb_forgery_protection"
3
+ task :default do
4
+ puts "merb_forgery_protection doesn't do anything"
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "merb_forgery_protection" do
4
+ it "should do nothing" do
5
+ true.should == true
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bchiu-merb_forgery_protection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Chiu
8
+ autorequire: merb_forgery_protection
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-02 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: merb
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.0
23
+ version:
24
+ description: Merb plugin that provides forgery protection against css attacks
25
+ email: bchiu@yahoo.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - LICENSE
33
+ - TODO
34
+ files:
35
+ - LICENSE
36
+ - README
37
+ - Rakefile
38
+ - TODO
39
+ - lib/merb_forgery_protection
40
+ - lib/merb_forgery_protection/forgery_protection.rb
41
+ - lib/merb_forgery_protection/form_helpers.rb
42
+ - lib/merb_forgery_protection/merbtasks.rb
43
+ - lib/merb_forgery_protection.rb
44
+ - spec/merb_forgery_protection_spec.rb
45
+ - spec/spec_helper.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/bchiu/merb_forgery_protection
48
+ post_install_message:
49
+ rdoc_options: []
50
+
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.0.1
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: Merb plugin that provides forgery protection against css attacks
72
+ test_files: []
73
+