ixtlan-core 0.6.0 → 0.6.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/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
|