Permalink
Browse files

replace Sinatra::CSRF with Rack::Protection

  • Loading branch information...
1 parent 6d8e69c commit aca8d1d42d6171c1862253e89716d06559ff6a78 @rkh rkh committed Aug 17, 2011
Showing with 105 additions and 241 deletions.
  1. +5 −0 Gemfile.lock
  2. +1 −1 lib/sinatra/contrib.rb
  3. +0 −128 lib/sinatra/csrf.rb
  4. +53 −0 lib/sinatra/protection.rb
  5. +1 −0 sinatra-contrib.gemspec
  6. +0 −112 spec/csrf_spec.rb
  7. +45 −0 spec/protection_spec.rb
View
5 Gemfile.lock
@@ -11,6 +11,7 @@ PATH
specs:
sinatra-contrib (1.3.0)
backports (>= 2.0)
+ rack-protection
rack-test
sinatra (~> 1.3.0)
tilt (~> 1.3)
@@ -21,10 +22,14 @@ GEM
backports (2.3.0)
diff-lcs (1.1.2)
erubis (2.7.0)
+ escape_utils (0.2.3)
haml (3.1.2)
json (1.5.1)
json (1.5.1-java)
rack (1.3.2)
+ rack-protection (0.1.0)
+ escape_utils
+ rack
rack-test (0.6.1)
rack (>= 1.0)
rake (0.9.2)
View
2 lib/sinatra/contrib.rb
@@ -8,8 +8,8 @@ module Contrib
# Sinatra::Application by default.
module Common
register :ConfigFile
- register :CSRF
register :Namespace
+ register :Protection
register :RespondWith
helpers :Capture
View
128 lib/sinatra/csrf.rb
@@ -1,128 +0,0 @@
-require 'sinatra/base'
-require 'forwardable'
-
-module Sinatra
- module CSRF
- SAFE_METHODS = %w[GET HEAD OPTIONS TRACE]
- TOKEN_HEADER = 'HTTP_X_CSRF_Token'
- TOKEN_FIELD = 'authenticity_token'
-
- def self.registered(base)
- base.enable :csrf_protection unless base.respond_to? :csrf_protection
- base.helpers Helpers
- base.use Middleware, base
- end
-
- module Helpers
- def authenticity_token
- session['sinatra.token']
- end
-
- def authenticity_tag
- return "" unless authenticity_token
- "<input type='hidden' name='#{TOKEN_FIELD}' value='#{authenticity_token}' />"
- end
-
- alias csrf_token authenticity_token
- alias csrf_tag authenticity_tag
- end
-
- class Middleware
- extend Forwardable
- def_delegators :@base, :csrf_protection?, :csrf_protection, :development?
-
- def initialize(app, base)
- @app, @base = app, base
- end
-
- def call(env)
- return @app.call(env) unless csrf_protection
- request = Sinatra::Request.new env
- set_token(request) if checks.include? :token
- safe?(request) ? @app.call(env) : response(request)
- end
-
- private
-
- def checks
- return @checks if defined? @checks
- checks = [:verb, *Array(csrf_protection)]
- checks.map! { |c| c == true ? :optional_referrer : c }
- checks.delete :verb if checks.delete :all_verbs
- @checks = checks
- end
-
- def set_token(request)
- request.session['sinatra.token'] ||= '%x' % rand(2**255)
- end
-
- def safe?(r)
- checks.any? { |c| send("safe_#{c}?", r) }
- end
-
- def safe_verb?(r)
- SAFE_METHODS.include? r.request_method
- end
-
- def safe_token?(r)
- token = request.session['sinatra.token']
- r.env[TOKEN_HEADER] == token or r[TOKEN_FIELD] == token
- end
-
- def safe_forms?(r)
- request.xhr? or safe_token?(r)
- end
-
- def safe_referrer?(r)
- return false if r.referrer.nil? or r.referrer.empty?
- host = URI.parse(r.referrer.to_s).host
- host.nil? or host == r.host
- end
-
- alias safe_referer? safe_referrer?
-
- def safe_optional_referrer?(r)
- r.referrer.nil? or r.referrer.empty? or safe_referrer?(r)
- end
-
- def response(r)
- response = Rack::Response.new
- response.status = 412
- if development?
- response.body << <<-HTML.gsub(/^ {12}/, '')
- <!DOCTYPE html>
- <html>
- <head>
- <style type="text/css">
- body { text-align:center;font-family:helvetica,arial;font-size:22px;
- color:#888;margin:20px}
- #c {margin:0 auto;width:500px;text-align:left}
- </style>
- </head>
- <body>
- <h2>Potentinal CSRF attack prevented!</h2>
- <img src='#{r.script_name}/__sinatra__/500.png'>
- <div id="c">
- <p>
- Sinatra automatically blocks unsafe requests coming from other
- hosts. If you want to allow such requests, please make sure
- you fully understand how CSRF attacks work first, and then,
- add the following line to your Sinatra application:
- </p>
- <pre>disable :csrf_protection</pre>
- <p>
- You can also change the CSRF counter measures like this:
- </p>
- <pre>set :csrf_protection, :token</pre>
- </div>
- </body>
- </html>
- HTML
- end
- response.finish
- end
- end
- end
-
- register CSRF
-end
View
53 lib/sinatra/protection.rb
@@ -0,0 +1,53 @@
+require 'sinatra/base'
+require 'rack/protection'
+
+module Sinatra
+
+ # = Sinatra::Protection
+ #
+ # Sets up {rack-protection}[https://github.com/rkh/rack-protection] to
+ # prevent common attacks against your application.
+ #
+ # == Usage
+ # The protection modes used can be configured by the +protection+ setting:
+ #
+ # require 'sinatra'
+ # require 'sinatra/protection'
+ #
+ # set :protection, :except => :path_traversal
+ #
+ # There are a few, partly protection specific options you can set, too:
+ #
+ # set :protection,
+ # :reaction => :deny, # block malicious requests, alternative: :drop_session
+ # :frame_options => :deny # do not allow any embedding in frames (default: :sameorigin)
+ #
+ # For more information, see rack-protection.
+ #
+ # === Classic Application
+ #
+ # As with any other extension, you have to register this one manually in a
+ # classic application:
+ #
+ # require 'sinatra/base'
+ # require 'sinatra/protection'
+ #
+ # class MyApp < Sinatra::Base
+ # register Sinatra::Protection
+ # end
+ module Protection
+ def setup_default_middleware(builder)
+ super
+ if protection
+ options = protection == true ? {} : protection
+ builder.use Rack::Protection, options
+ end
+ end
+
+ def self.registered(base)
+ base.enable :protection
+ end
+ end
+
+ register Sinatra::Namespace
+end
View
1 sinatra-contrib.gemspec
@@ -106,6 +106,7 @@ Gem::Specification.new do |s|
s.add_dependency "backports", ">= 2.0"
s.add_dependency "tilt", "~> 1.3"
s.add_dependency "rack-test"
+ s.add_dependency "rack-protection"
s.add_development_dependency "rspec", "~> 2.3"
s.add_development_dependency "haml"
View
112 spec/csrf_spec.rb
@@ -1,112 +0,0 @@
-require 'backports'
-require_relative 'spec_helper'
-
-describe Sinatra::CSRF do
- SAFE = %w[get options]
- UNSAFE = %w[post put delete]
- UNSAFE << "patch" if Sinatra::Base.respond_to? :patch
-
- def self.checks(*list)
- before do
- mock_app do
- register Sinatra::CSRF
- set :csrf_protection, list
- (SAFE + UNSAFE).each { |v| send(v, '/') { 'ok' }}
- get('/token') { authenticity_token }
- get('/tag') { authenticity_tag }
- end
- end
- end
-
- describe 'optional referrer' do
- checks :optional_referrer
-
- UNSAFE.each do |verb|
- it "prevents #{verb} requests from a different host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://google.com')
- last_response.should_not be_ok
- end
-
- it "allows #{verb} requests from the same host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://example.org')
- last_response.should be_ok
- end
-
- it "allows #{verb} requests with no referrer" do
- send(verb, '/')
- last_response.should be_ok
- end
- end
-
- SAFE.each do |verb|
- it "allows #{verb} requests from a different host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://google.com')
- last_response.should be_ok
- end
-
- it "allows #{verb} requests from the same host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://example.org')
- last_response.should be_ok
- end
-
- it "allows #{verb} requests with no referrer" do
- send(verb, '/', {}, 'HTTP_REFERER' => '')
- last_response.should be_ok
- end
- end
- end
-
- describe 'referrer' do
- checks :referrer
-
- UNSAFE.each do |verb|
- it "prevents #{verb} requests from a different host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://google.com')
- last_response.should_not be_ok
- end
-
- it "allows #{verb} requests from the same host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://example.org')
- last_response.should be_ok
- end
-
- it "allows #{verb} requests with no referrer" do
- send(verb, '/', {}, 'HTTP_REFERER' => '')
- last_response.should_not be_ok
- end
- end
-
- SAFE.each do |verb|
- it "allows #{verb} requests from a different host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://google.com')
- last_response.should be_ok
- end
-
- it "allows #{verb} requests from the same host" do
- send(verb, '/', {}, 'HTTP_REFERER' => 'http://example.org')
- last_response.should be_ok
- end
-
- it "allows #{verb} requests with no referrer" do
- send(verb, '/', {}, 'HTTP_REFERER' => '')
- last_response.should be_ok
- end
- end
- end
-
- describe 'token' do
- checks :token
- it 'prevents normal requests without a valid token'
- it 'prevents ajax requests without a valid token'
- it 'allows normal requests with a valid token'
- it 'allows ajax requests with a valid token'
- end
-
- describe 'form' do
- checks :form
- it 'prevents normal requests without a valid token'
- it 'allows ajax requests without a valid token'
- it 'allows normal requests with a valid token'
- it 'allows ajax requests with a valid token'
- end
-end
View
45 spec/protection_spec.rb
@@ -0,0 +1,45 @@
+require 'backports'
+require_relative 'spec_helper'
+
+class MiddlewareTracker < Rack::Builder
+ def self.used
+ @used ||= []
+ end
+
+ def use(middleware, *)
+ MiddlewareTracker.used << middleware
+ super
+ end
+end
+
+describe Sinatra::Protection do
+ before do
+ Rack.send :remove_const, :Builder
+ Rack.const_set :Builder, MiddlewareTracker
+ MiddlewareTracker.used.clear
+ end
+
+ after do
+ Rack.send :remove_const, :Builder
+ Rack.const_set :Builder, MiddlewareTracker.superclass
+ end
+
+ it 'sets up Rack::Protection' do
+ Sinatra.new { register Sinatra::Protection }.new
+ MiddlewareTracker.used.should include(Rack::Protection)
+ end
+
+ it 'sets up Rack::Protection::PathTraversal by default' do
+ Sinatra.new { register Sinatra::Protection }.new
+ MiddlewareTracker.used.should include(Rack::Protection::PathTraversal)
+ end
+
+
+ it 'does not set up Rack::Protection::PathTraversal when disabling it' do
+ Sinatra.new do
+ register Sinatra::Protection
+ set :protection, :except => :path_traversal
+ end.new
+ MiddlewareTracker.used.should_not include(Rack::Protection::PathTraversal)
+ end
+end

0 comments on commit aca8d1d

Please sign in to comment.