content-security-policy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2012 Alexey Rodionov
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.md ADDED
@@ -0,0 +1,66 @@
1
+ ## Content Security Policy
2
+
3
+ [![Build Status](https://secure.travis-ci.org/p0deje/content-security-policy.png)](http://travis-ci.org/p0deje/content-security-policy)
4
+
5
+ Implementation of Content Security Policy as Rack middleware.
6
+
7
+ More information about Content Security Policy - http://www.w3.org/TR/CSP/.
8
+
9
+ ## Installation
10
+
11
+ Install as usually `gem install content-security-policy`
12
+
13
+ ## Usage
14
+
15
+ Add Content Security Policy to your Rack configuration `config.ru`.
16
+
17
+ ```ruby
18
+ require 'content-security-policy'
19
+
20
+ ContentSecurityPolicy.configure do |csp|
21
+ csp['default-src'] = "'self'"
22
+ csp['script-src'] = '*.example.com'
23
+ end
24
+
25
+ use ContentSecurityPolicy
26
+ run MyApplication
27
+ ```
28
+
29
+ You can also pass directives during initialization.
30
+
31
+ ```ruby
32
+ require 'content-security-policy'
33
+
34
+ use ContentSecurityPolicy, :directives => { 'policy-uri' => 'policy.xml' }
35
+ run MyApplication
36
+ ```
37
+
38
+ You can also use report-only mode.
39
+
40
+ ```ruby
41
+ require 'content-security-policy'
42
+
43
+ ContentSecurityPolicy.configure do |csp|
44
+ csp.report_only = true
45
+ csp['default-src'] = "'self'"
46
+ csp['script-src'] = '*.example.com'
47
+ end
48
+
49
+ use ContentSecurityPolicy
50
+ run MyApplication
51
+ ```
52
+
53
+ ```ruby
54
+ require 'content-security-policy'
55
+
56
+ use ContentSecurityPolicy, :directives => { 'policy-uri' => 'policy.xml' }, :report_only => true
57
+ run MyApplication
58
+ ```
59
+
60
+ ## Status
61
+
62
+ Content Security Policy is now implemented with `X-Content-Security-Policy` and `X-WebKit-CSP` headers.
63
+
64
+ ## Copyright
65
+
66
+ Copyright (c) 2012 Alexey Rodionov. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ RSpec::Core::RakeTask.new :spec do |spec|
7
+ spec.ruby_opts = "-I lib:spec"
8
+ spec.pattern = 'spec/**/*_spec.rb'
9
+ end
10
+
11
+ task :default => :spec
12
+
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ require 'content-security-policy/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'content-security-policy'
6
+ s.version = ContentSecurityPolicy::VERSION
7
+
8
+ s.author = 'Alex Rodionov'
9
+ s.email = 'p0deje@gmail.com'
10
+
11
+ s.homepage = 'https://github.com/p0deje/content-security-policy'
12
+ s.summary = 'Full-featured Content Security Policy as Rack middleware'
13
+ s.description = 'Full-featured Content Security Policy as Rack middleware'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
18
+
19
+ s.require_path = 'lib'
20
+
21
+ s.add_dependency 'rack'
22
+
23
+ s.add_development_dependency 'rack-test'
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,45 @@
1
+ require 'content-security-policy/middleware'
2
+ require 'content-security-policy/errors'
3
+ require 'content-security-policy/version'
4
+
5
+ class ContentSecurityPolicy
6
+ class << self
7
+
8
+ # @attr_accessor [Boolean] use in report only mode
9
+ attr_accessor :report_only
10
+ # @attr_reader [Hash] directives hash
11
+ attr_reader :directives
12
+
13
+ #
14
+ # Configures Content Security Policy directives.
15
+ #
16
+ # Note that default-src directive should always be set.
17
+ #
18
+ # @example
19
+ # ContentSecurityPolicy.configure do |csp|
20
+ # csp.report_only = true
21
+ # csp['default-src'] = "'self'"
22
+ # csp['script-src'] = '*.example.com'
23
+ # end
24
+ # use ContentSecurityPolicy
25
+ #
26
+ # @yield [self]
27
+ #
28
+ def configure(&blk)
29
+ @directives ||= {}
30
+ blk.call(self)
31
+ end
32
+
33
+ #
34
+ # Sets directive.
35
+ #
36
+ # @param [String] name Directive name
37
+ # @param [String] value Directive value
38
+ #
39
+ def []=(name, value)
40
+ @directives[name] = value
41
+ end
42
+
43
+ end # << self
44
+ end # ContentSecurityPolicy
45
+
@@ -0,0 +1,7 @@
1
+ class ContentSecurityPolicy
2
+
3
+ class NoDirectivesError < StandardError; end
4
+ class IncorrectDirectivesError < StandardError; end
5
+
6
+ end # ContentSecurityPolicy
7
+
@@ -0,0 +1,67 @@
1
+ class ContentSecurityPolicy
2
+
3
+ # @attr_reader [Boolean] use in report only mode
4
+ attr_reader :report_only
5
+
6
+ # @attr_reader [Hash] directives hash
7
+ attr_reader :directives
8
+
9
+ #
10
+ # Initializes Content Security Policy middleware.
11
+ #
12
+ # @param [Hash] opts Options hash
13
+ # @option [Boolean] :report_only Set to true if use in report-only mode
14
+ # @option [Hash] :directives Directives
15
+ #
16
+ # @example
17
+ # use ContentSecurityPolicy, :directives => { 'default-src' => "'self'" }
18
+ # use ContentSecurityPolicy, :directives => { 'default-src' => "'self'", :report_only => true }
19
+ #
20
+ def initialize(app, options = {})
21
+ @app = app
22
+ @report_only = options[:report_only] || ContentSecurityPolicy.report_only
23
+ @directives = options[:directives] || ContentSecurityPolicy.directives
24
+
25
+ @directives or raise NoDirectivesError, 'No directives were passed.'
26
+
27
+ # make sure directives with policy-uri don't contain any other directives
28
+ if @directives['policy-uri'] && @directives.keys.length > 1
29
+ raise IncorrectDirectivesError, 'You passed both policy-uri and other directives.'
30
+ # make sure default-src is present
31
+ elsif !@directives['policy-uri'] && !@directives['default-src']
32
+ raise IncorrectDirectivesError, 'You have to set default-src directive.'
33
+ end
34
+ end
35
+
36
+ #
37
+ # @api private
38
+ #
39
+ def call(env)
40
+ dup._call(env)
41
+ end
42
+
43
+ #
44
+ # @api private
45
+ #
46
+ def _call(env)
47
+ status, headers, response = @app.call(env)
48
+
49
+ # flatten directives
50
+ directives = @directives.sort.map { |dir| "#{dir[0]} #{dir[1]}" }.join('; ')
51
+
52
+ # prepare response headers names
53
+ if @report_only
54
+ resp_headers = %w(X-Content-Security-Policy-Report-Only X-WebKit-CSP-Report-Only)
55
+ else
56
+ resp_headers = %w(X-Content-Security-Policy X-WebKit-CSP)
57
+ end
58
+
59
+ # append response header
60
+ resp_headers.each do |resp_header|
61
+ headers[resp_header] = directives
62
+ end
63
+
64
+ [status, headers, response]
65
+ end
66
+
67
+ end # ContentSecurityPolicy
@@ -0,0 +1,5 @@
1
+ class ContentSecurityPolicy
2
+
3
+ VERSION = '0.1.1'
4
+
5
+ end # ContentSecurityPolicy
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContentSecurityPolicy do
4
+
5
+ context 'configuration' do
6
+ let(:app) do
7
+ [200, { 'Content-Type' => 'text/plain' }, %w(ok)]
8
+ end
9
+
10
+ describe '#initialize' do
11
+ it 'should raise error if directives hash is not present' do
12
+ lambda do
13
+ ContentSecurityPolicy.new(app)
14
+ end.should raise_error(ContentSecurityPolicy::NoDirectivesError, 'No directives were passed.')
15
+ end
16
+
17
+ it 'should raise error if default-src was not set' do
18
+ lambda do
19
+ options = { :directives => { 'script-src' => "'self'" }}
20
+ ContentSecurityPolicy.new(app, options)
21
+ end.should raise_error(ContentSecurityPolicy::IncorrectDirectivesError, 'You have to set default-src directive.')
22
+ end
23
+
24
+ it 'should raise error if both policy-uri and other directive was set' do
25
+ lambda do
26
+ options = { :directives => { 'policy-uri' => 'policy.xml', 'script-src' => "'self'" }}
27
+ ContentSecurityPolicy.new(app, options)
28
+ end.should raise_error(ContentSecurityPolicy::IncorrectDirectivesError, "You passed both policy-uri and other directives.")
29
+ end
30
+
31
+ it 'should allow setting directives with ContentSecurityPolicy.configure' do
32
+ ContentSecurityPolicy.configure { |csp| csp['default-src'] = "'self'" }
33
+ ContentSecurityPolicy.should_receive(:directives).and_return('default-src' => '*')
34
+
35
+ lambda do
36
+ ContentSecurityPolicy.new(app)
37
+ end.should_not raise_error(ContentSecurityPolicy::NoDirectivesError, 'No directives were passed.')
38
+ end
39
+
40
+ it 'should allow passing hash of directives' do
41
+ lambda do
42
+ options = { :directives => { 'default-src' => "'self'" }}
43
+ ContentSecurityPolicy.new(app, options)
44
+ end.should_not raise_error
45
+ end
46
+
47
+ it 'should allow passing report_only attribute' do
48
+ lambda do
49
+ options = { :directives => { 'default-src' => "'self'" }, :report_only => true }
50
+ ContentSecurityPolicy.new(app, options)
51
+ end.should_not raise_error
52
+ end
53
+ end
54
+
55
+ describe '#configure' do
56
+ it 'should call block for self' do
57
+ ContentSecurityPolicy.should_receive(:configure).and_yield(ContentSecurityPolicy)
58
+ ContentSecurityPolicy.configure { |csp| csp['default-src'] = '*' }
59
+ end
60
+
61
+ it 'should save directives hash' do
62
+ ContentSecurityPolicy.configure { |csp| csp['default-src'] = '*' }
63
+ ContentSecurityPolicy.directives.should == { 'default-src' => '*' }
64
+ end
65
+
66
+ it 'should append directives' do
67
+ ContentSecurityPolicy.configure { |csp| csp['default-src'] = '*' }
68
+ ContentSecurityPolicy.configure { |csp| csp['script-src'] = '*' }
69
+ ContentSecurityPolicy.directives.should == { 'default-src' => '*',
70
+ 'script-src' => '*' }
71
+ end
72
+
73
+ it 'should save report_only attribute' do
74
+ ContentSecurityPolicy.configure { |csp| csp.report_only = true }
75
+ ContentSecurityPolicy.report_only.should be_true
76
+ end
77
+ end
78
+ end
79
+
80
+ context 'middleware' do
81
+ let(:app) do
82
+ Rack::Builder.app do
83
+ use ContentSecurityPolicy
84
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, %w(ok)] }
85
+ end
86
+ end
87
+
88
+ before(:each) do
89
+ ContentSecurityPolicy.configure do |csp|
90
+ csp.report_only = false
91
+ csp['default-src'] = '*'
92
+ csp['script-src'] = "'self'"
93
+ csp['img-src'] = '*.google.com'
94
+ end
95
+ end
96
+
97
+ describe '#call' do
98
+ it 'should respond with X-Content-Security-Policy HTTP response header' do
99
+ directives = "default-src *; img-src *.google.com; script-src 'self'"
100
+
101
+ header = get('/').headers['X-Content-Security-Policy']
102
+ header.should_not be_nil
103
+ header.should_not be_empty
104
+ header.should == directives
105
+ end
106
+
107
+ it 'should respond with X-WebKit-CSP HTTP response header' do
108
+ directives = "default-src *; img-src *.google.com; script-src 'self'"
109
+
110
+ header = get('/').headers['X-WebKit-CSP']
111
+ header.should_not be_nil
112
+ header.should_not be_empty
113
+ header.should == directives
114
+ end
115
+
116
+ it 'should respond with X-Content-Security-Policy-Report-Only HTTP response header' do
117
+ ContentSecurityPolicy.configure { |csp| csp.report_only = true }
118
+ directives = "default-src *; img-src *.google.com; script-src 'self'"
119
+
120
+ header = get('/').headers['X-Content-Security-Policy-Report-Only']
121
+ header.should_not be_nil
122
+ header.should_not be_empty
123
+ header.should == directives
124
+ end
125
+
126
+ it 'should respond with X-WebKit-CSP HTTP response header' do
127
+ ContentSecurityPolicy.configure { |csp| csp.report_only = true }
128
+ directives = "default-src *; img-src *.google.com; script-src 'self'"
129
+
130
+ header = get('/').headers['X-WebKit-CSP-Report-Only']
131
+ header.should_not be_nil
132
+ header.should_not be_empty
133
+ header.should == directives
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ require 'content-security-policy'
3
+ require 'rack/test'
4
+
5
+ RSpec.configure do |config|
6
+ config.include Rack::Test::Methods
7
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: content-security-policy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Rodionov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-17 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: &21497820 !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: *21497820
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack-test
27
+ requirement: &21497260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *21497260
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &21496620 !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: *21496620
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &21495520 !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: *21495520
58
+ description: Full-featured Content Security Policy as Rack middleware
59
+ email: p0deje@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - .travis.yml
65
+ - Gemfile
66
+ - LICENSE
67
+ - README.md
68
+ - Rakefile
69
+ - content-security-policy.gemspec
70
+ - lib/content-security-policy.rb
71
+ - lib/content-security-policy/errors.rb
72
+ - lib/content-security-policy/middleware.rb
73
+ - lib/content-security-policy/version.rb
74
+ - spec/content-security-policy_spec.rb
75
+ - spec/spec_helper.rb
76
+ homepage: https://github.com/p0deje/content-security-policy
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.16
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Full-featured Content Security Policy as Rack middleware
100
+ test_files:
101
+ - spec/content-security-policy_spec.rb
102
+ - spec/spec_helper.rb
103
+ has_rdoc: