content-security-policy 0.1.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/.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: