ixtlan-core 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +64 -0
- data/features/headers.feature +12 -0
- data/features/step_definitions/simple_steps.rb +1 -0
- data/lib/ixtlan/core/active_record.rb +22 -0
- data/lib/ixtlan/core/active_record.rb~ +24 -0
- data/lib/ixtlan/core/controllers/configuration_controller.rb~ +9 -0
- data/lib/ixtlan/core/data_mapper.rb +23 -0
- data/lib/ixtlan/core/data_mapper.rb~ +18 -0
- data/lib/ixtlan/core/optimistic_active_record.rb~ +21 -0
- data/lib/ixtlan/core/{optimistic_data_mapper.rb → optimistic_data_mapper.rb~} +1 -1
- data/lib/ixtlan/core/railtie.rb +6 -6
- data/lib/ixtlan/core/x_content_headers.rb~ +32 -0
- data/lib/ixtlan/core/x_content_type_headers.rb~ +30 -0
- data/lib/ixtlan/core/x_xss_protection_headers.rb~ +30 -0
- data/spec/cache_headers_spec.rb~ +52 -0
- data/spec/configuration_manager_spec.rb~ +40 -0
- data/spec/controller.rb~ +8 -0
- data/spec/x_headers_spec.rb~ +74 -0
- metadata +24 -37
- data/lib/generators/ixtlan/base.rb +0 -45
- data/lib/generators/ixtlan/configuration_model/configuration_model_generator.rb +0 -12
- data/lib/generators/ixtlan/configuration_scaffold/configuration_scaffold_generator.rb +0 -12
- data/lib/generators/ixtlan/setup/setup_generator.rb +0 -38
- data/lib/generators/ixtlan/setup/templates/application_layout.html.erb +0 -17
- data/lib/generators/ixtlan/setup/templates/database.yml.example +0 -48
- data/lib/generators/ixtlan/setup/templates/error.html.erb +0 -1
- data/lib/generators/ixtlan/setup/templates/error_with_session.html.erb +0 -1
- data/lib/generators/ixtlan/setup/templates/gitignore +0 -2
- data/lib/generators/ixtlan/setup/templates/initializer.rb +0 -64
- data/lib/generators/ixtlan/setup/templates/preinitializer.rb +0 -31
- data/lib/generators/ixtlan/setup/templates/production.yml.example +0 -8
- data/lib/generators/ixtlan/setup/templates/stale.html.erb +0 -2
- data/lib/generators/model/model_generator.rb +0 -12
- data/lib/generators/rails/active_record/active_record_generator.rb +0 -40
- data/lib/generators/rails/active_record/model/migration.rb +0 -19
- data/lib/generators/rails/active_record/model/model.rb +0 -16
- data/lib/generators/rails/erb/erb_generator.rb +0 -37
- data/lib/generators/rails/erb/scaffold/_form.html.erb +0 -28
- data/lib/generators/rails/erb/scaffold/edit.html.erb +0 -24
- data/lib/generators/rails/erb/scaffold/index.html.erb +0 -49
- data/lib/generators/rails/erb/scaffold/new.html.erb +0 -11
- data/lib/generators/rails/erb/scaffold/show.html.erb +0 -30
- data/lib/generators/rails/scaffold_controller/scaffold_controller/controller.rb +0 -129
- data/lib/generators/rails/scaffold_controller/scaffold_controller/singleton_controller.rb +0 -43
- data/lib/generators/scaffold/scaffold_generator.rb +0 -35
- data/lib/generators/scaffold_controller/scaffold_controller_generator.rb +0 -34
- data/lib/ixtlan/core/optimistic_active_record.rb +0 -18
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Ixtlan #
|
2
|
+
|
3
|
+
this gem adds more security related headers to the response for a rails3 application. mainly inspired by
|
4
|
+
[google-gets-a-1-for-browser-security](http://www.barracudalabs.com/wordpress/index.php/2011/07/21/google-gets-a-1-for-browser-security-3/)
|
5
|
+
and
|
6
|
+
[HttpCaching](http://code.google.com/p/doctype/wiki/ArticleHttpCaching).
|
7
|
+
and
|
8
|
+
[Clickjacking](http://www.owasp.org/index.php/Clickjacking)
|
9
|
+
|
10
|
+
the extra headers are
|
11
|
+
|
12
|
+
* x-frame headers
|
13
|
+
* x-content-type headers
|
14
|
+
* x-xss-protection headers
|
15
|
+
* caching headers
|
16
|
+
|
17
|
+
the main idea is to set the default as strict as possible and the application might relax the setup here and there.
|
18
|
+
|
19
|
+
## rails configuration ##
|
20
|
+
|
21
|
+
in _config/application.rb_ or in one of the _config/environments/*rb_ files or in an initializer. all three x-headers can be configured here, for example
|
22
|
+
|
23
|
+
config.x_content_type_headers = :nosniff
|
24
|
+
|
25
|
+
## controller configuration ##
|
26
|
+
|
27
|
+
just add in your controller something like
|
28
|
+
|
29
|
+
x_xss_protection :block
|
30
|
+
|
31
|
+
## option for each *render*, *send\_file*, *send\_data* methods
|
32
|
+
|
33
|
+
an example for an inline render
|
34
|
+
|
35
|
+
render :inline => 'behappy', :x_frame_headers => :deny
|
36
|
+
|
37
|
+
## possible values ##
|
38
|
+
|
39
|
+
* x\_frame\_headers : `:deny, :sameorigin, :off` default `:deny`
|
40
|
+
|
41
|
+
* x\_content\_type\_headers : `:nosniff, :off` default `:nosniff`
|
42
|
+
|
43
|
+
* x\_xss\_protection\_headers : `:block, :disabled, :off` default `:block`
|
44
|
+
|
45
|
+
## cache headers
|
46
|
+
|
47
|
+
the cache headers needs to have a **current\_user**, i.e. the current\_user method of the controller needs to return a non-nil value. further the the method needs to `:get` and the response status an "ok" status,
|
48
|
+
|
49
|
+
then you can use the controller configuration or the options with *render*, *send\_file* and *send\_data*.
|
50
|
+
|
51
|
+
## possible values ##
|
52
|
+
|
53
|
+
* `:private` : which tells not to cache or store any data except the browser memory: [no caching](http://code.google.com/p/doctype/wiki/ArticleHttpCaching#No_caching)
|
54
|
+
|
55
|
+
* `:protected` : no caching but the browser: [Only the end user's browser is allowed to cache](http://code.google.com/p/doctype/wiki/ArticleHttpCaching#Only_the_end_user%27s_browser_is_allowed_to_cache)
|
56
|
+
|
57
|
+
* `:public` : caching is allowed: [Both browser and proxy allowed to cache](http://code.google.com/p/doctype/wiki/ArticleHttpCaching#Both_browser_and_proxy_allowed_to_cache)
|
58
|
+
|
59
|
+
* `:my_headers` : custom header method like
|
60
|
+
|
61
|
+
> def my_headers
|
62
|
+
no_store = false
|
63
|
+
no_caching(no_store)
|
64
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Generators for Ixtlan Core
|
2
|
+
|
3
|
+
Scenario: Create a rails application and adding the ixtlan-core adds ixtlan generators
|
4
|
+
Given I create new rails application with template "headers.template" and "headers" tests
|
5
|
+
And I execute "rails generate scaffold user name:string --skip --migration"
|
6
|
+
And I execute "rake db:migrate test"
|
7
|
+
Then the output should contain "7 tests, 20 assertions, 0 failures, 0 errors" and "1 tests, 1 assertions, 0 failures, 0 errors"
|
8
|
+
|
9
|
+
Given me an existing rails application "headers" and "optimistic" files
|
10
|
+
And I execute "rails generate scaffold account name:string --skip --migration --timestamps --optimistic --modified-by user"
|
11
|
+
And I execute "rake db:migrate test"
|
12
|
+
Then the output should contain "14 tests, 30 assertions, 0 failures, 0 errors" and "2 tests, 2 assertions, 0 failures, 0 errors"
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'maven/cucumber_steps'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
def self.optimistic_find(updated_at, *args)
|
9
|
+
if updated_at
|
10
|
+
updated_at_date = new(:updated_at => updated_at).updated_at
|
11
|
+
# try different ways to use the date
|
12
|
+
# TODO maybe there is a nicer way ??
|
13
|
+
first(:conditions => ["id = ? and updated_at <= ? and updated_at >= ?", args[0], updated_at, updated_at_date - 0.001]) || first(:conditions => ["id = ? and updated_at <= ? and updated_at >= ?", args[0], updated_at_date, updated_at_date - 0.001])
|
14
|
+
# TODO make it work with different PKs
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module OptimisticActiveRecord
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
attr_accessor :current_user
|
9
|
+
|
10
|
+
def self.optimistic_find(updated_at, *args)
|
11
|
+
if updated_at
|
12
|
+
updated_at_date = new(:updated_at => updated_at).updated_at
|
13
|
+
# try different ways to use the date
|
14
|
+
# TODO maybe there is a nicer way ??
|
15
|
+
first(:conditions => ["id = ? and updated_at <= ? and updated_at >= ?", args[0], updated_at, updated_at_date - 0.001]) || first(:conditions => ["id = ? and updated_at <= ? and updated_at >= ?", args[0], updated_at_date, updated_at_date - 0.001])
|
16
|
+
# TODO make it work with different PKs
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module DataMapper
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
attr_accessor :current_user
|
9
|
+
|
10
|
+
def optimistic_find(updated_at, *args)
|
11
|
+
if updated_at
|
12
|
+
updated_at = new(:updated_at => updated_at).updated_at
|
13
|
+
# TODO make it work with different PKs
|
14
|
+
first(:id => args[0], :updated_at => updated_at)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module OptimisticDataMapper
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
def optimistic_find(updated_at, *args)
|
8
|
+
if updated_at
|
9
|
+
updated_at = new(:updated_at => updated_at).updated_at
|
10
|
+
# TODO make it work with different PKs
|
11
|
+
first(:id => args[0], :updated_at => updated_at)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module OptimisticActiveRecord
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
def self.optimistic_find(updated_at, *args)
|
8
|
+
p updated_at
|
9
|
+
updated_at = new(:updated_at => updated_at).updated_at
|
10
|
+
# TODO make it work with different PKs
|
11
|
+
r = first(:conditions => ["id = ? and updated_at = ?", args[0], updated_at])
|
12
|
+
p args[0]
|
13
|
+
p updated_at
|
14
|
+
p r
|
15
|
+
r
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -7,7 +7,7 @@ module Ixtlan
|
|
7
7
|
def optimistic_find(updated_at, *args)
|
8
8
|
updated_at = new(:updated_at => updated_at).updated_at
|
9
9
|
# TODO make it work with different PKs
|
10
|
-
first(:id => args[0], :updated_at => updated_at)
|
10
|
+
first(:id => args[0], :updated_at => updated_at])
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/ixtlan/core/railtie.rb
CHANGED
@@ -3,8 +3,8 @@ require 'ixtlan/core/cache_headers'
|
|
3
3
|
require 'ixtlan/core/x_frame_headers'
|
4
4
|
require 'ixtlan/core/x_content_type_headers'
|
5
5
|
require 'ixtlan/core/x_xss_protection_headers'
|
6
|
-
require 'ixtlan/core/
|
7
|
-
require 'ixtlan/core/
|
6
|
+
require 'ixtlan/core/active_record'
|
7
|
+
require 'ixtlan/core/data_mapper'
|
8
8
|
require 'ixtlan/core/configuration_rack'
|
9
9
|
require 'ixtlan/core/configuration_manager'
|
10
10
|
module Ixtlan
|
@@ -63,15 +63,15 @@ module Ixtlan
|
|
63
63
|
app.config.middleware.use Ixtlan::Core::ConfigurationRack
|
64
64
|
end
|
65
65
|
config.after_initialize do |app|
|
66
|
-
if defined? DataMapper
|
66
|
+
if defined? ::DataMapper
|
67
67
|
|
68
68
|
::DataMapper::Resource.send(:include,
|
69
|
-
Ixtlan::Core::
|
69
|
+
Ixtlan::Core::DataMapper)
|
70
70
|
|
71
|
-
elsif defined? ActiveRecord
|
71
|
+
elsif defined? ::ActiveRecord
|
72
72
|
|
73
73
|
::ActiveRecord::Base.send(:include,
|
74
|
-
Ixtlan::Core::
|
74
|
+
Ixtlan::Core::ActiveRecord)
|
75
75
|
|
76
76
|
end
|
77
77
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module XFrameHeaders
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def x_frame_headers(mode = nil)
|
8
|
+
case mode || self.class.instance_variable_get(:@_x_frame_mode) || Rails.configuration.x_frame_headers
|
9
|
+
when :deny
|
10
|
+
response.headers["X-FRAME-OPTIONS"] = "DENY"
|
11
|
+
when :sameorigin
|
12
|
+
response.headers["X-FRAME-OPTIONS"] = "SAMEORIGIN"
|
13
|
+
when :off
|
14
|
+
else
|
15
|
+
warn "allowed values for x_frame_headers are :deny, :sameorigin, :off"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
def self.x_frame_headers(mode)
|
22
|
+
if(mode)
|
23
|
+
@_x_frame_mode = mode.to_sym
|
24
|
+
else
|
25
|
+
@_x_frame_mode = nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module XContentHeaders
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def x_content_headers(mode = nil)
|
8
|
+
case mode || self.class.instance_variable_get(:@_x_content_headers) || Rails.configuration.x_content_headers || :nosniff
|
9
|
+
when :nosniff
|
10
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
11
|
+
when :off
|
12
|
+
else
|
13
|
+
warn "allowed values for x_content_headers are :nosniff, :off"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.class_eval do
|
19
|
+
def self.x_content_headers(mode)
|
20
|
+
if(mode)
|
21
|
+
@_x_content_headers = mode.to_sym
|
22
|
+
else
|
23
|
+
@_x_content_headers = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Core
|
3
|
+
module XContentHeaders
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def x_content_headers(mode = nil)
|
8
|
+
case mode || self.class.instance_variable_get(:@_x_content_headers) || Rails.configuration.x_content_headers || :nosniff
|
9
|
+
when :nosniff
|
10
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
11
|
+
when :off
|
12
|
+
else
|
13
|
+
warn "allowed values for x_content_headers are :nosniff, :off"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.class_eval do
|
19
|
+
def self.x_content_headers(mode)
|
20
|
+
if(mode)
|
21
|
+
@_x_content_headers = mode.to_sym
|
22
|
+
else
|
23
|
+
@_x_content_headers = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'controller'
|
2
|
+
|
3
|
+
|
4
|
+
class MyControllerWithUser < ControllerWithUser
|
5
|
+
cache_headers :private
|
6
|
+
end
|
7
|
+
|
8
|
+
[:render#, :send_file, :send_data
|
9
|
+
].each do |method|
|
10
|
+
describe "cache-headers using controller method #{method}" do
|
11
|
+
context "with simple controller" do
|
12
|
+
subject { ControllerWithUser.new(Object.new) }
|
13
|
+
|
14
|
+
it 'should use default' do
|
15
|
+
subject.send method, :inline => "asd"
|
16
|
+
subject.response.headers.should == {"X-Frame-Options"=>"DENY", "X-Content-Type-Options"=>"nosniff", "X-XSS-Protection"=>"1; mode=block"}
|
17
|
+
end
|
18
|
+
it 'should use given option' do
|
19
|
+
subject.send method, :inline => "asd", :cache_headers => :protected
|
20
|
+
subject.response.headers.delete("Date").should_not be_nil
|
21
|
+
subject.response.headers.delete("Expires").should_not be_nil
|
22
|
+
subject.response.headers.should == {"Cache-Control"=>"private, max-age=0", "X-Frame-Options"=>"DENY", "X-Content-Type-Options"=>"nosniff", "X-XSS-Protection"=>"1; mode=block"}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with controller with header configuration" do
|
27
|
+
subject { MyControllerWithUser.new(Object.new) }
|
28
|
+
|
29
|
+
it 'should use configuration' do
|
30
|
+
subject.send method, :inline => "asd"
|
31
|
+
subject.response.headers.delete("Date").should_not be_nil
|
32
|
+
subject.response.headers.delete("Expires").should_not be_nil
|
33
|
+
subject.response.headers.should == {"Pragma"=>"no-cache", "Cache-Control"=>"no-cache, must-revalidate, no-store", "X-Frame-Options"=>"DENY", "X-Content-Type-Options"=>"nosniff", "X-XSS-Protection"=>"1; mode=block"}
|
34
|
+
end
|
35
|
+
it 'should use given option' do
|
36
|
+
subject.send method, :inline => "asd", :cache_headers => :public
|
37
|
+
subject.response.headers.delete("Date").should_not be_nil
|
38
|
+
subject.response.headers.delete("Expires").should_not be_nil
|
39
|
+
subject.response.headers.should == {"Cache-Control"=>"private, max-age=0", "X-Frame-Options"=>"DENY", "X-Content-Type-Options"=>"nosniff", "X-XSS-Protection"=>"1; mode=block"}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with simple controller without user" do
|
44
|
+
subject { MyControllerWithUser.new }
|
45
|
+
|
46
|
+
it 'should use default' do
|
47
|
+
subject.send method, :inline => "asd"
|
48
|
+
subject.response.headers.should == {"X-Frame-Options"=>"DENY", "X-Content-Type-Options"=>"nosniff", "X-XSS-Protection"=>"1; mode=block"}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ixtlan/core/configuration_manager'
|
2
|
+
|
3
|
+
class ConfigModel
|
4
|
+
|
5
|
+
def self.instance
|
6
|
+
@instance ||= self.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.after_save(method)
|
10
|
+
@method = method.to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.save_method
|
14
|
+
@method
|
15
|
+
end
|
16
|
+
|
17
|
+
def save
|
18
|
+
sent self.class.save_method
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Ixtlan::Core::ConfigurationManager do
|
23
|
+
|
24
|
+
# before :all do
|
25
|
+
# ConfigModel.sent :include, Ixtlan::Core::ConfigurationManager
|
26
|
+
# end
|
27
|
+
|
28
|
+
it "should" do
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should register listeners and fire change events" do
|
32
|
+
# count = 0
|
33
|
+
# ConfigModel.instance.register("counter") do
|
34
|
+
# count += 1
|
35
|
+
# end
|
36
|
+
# ConfigModel.instance.save
|
37
|
+
# count.should == 1
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|