andrewzielinski-lockdown 0.9.6
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/History.txt +195 -0
- data/README.txt +36 -0
- data/Rakefile +41 -0
- data/lib/lockdown.rb +70 -0
- data/lib/lockdown/context.rb +41 -0
- data/lib/lockdown/database.rb +105 -0
- data/lib/lockdown/frameworks/rails.rb +146 -0
- data/lib/lockdown/frameworks/rails/controller.rb +147 -0
- data/lib/lockdown/frameworks/rails/view.rb +61 -0
- data/lib/lockdown/helper.rb +95 -0
- data/lib/lockdown/orms/active_record.rb +68 -0
- data/lib/lockdown/permission.rb +204 -0
- data/lib/lockdown/rules.rb +289 -0
- data/lib/lockdown/session.rb +57 -0
- data/lib/lockdown/system.rb +57 -0
- data/rails_generators/lockdown/lockdown_generator.rb +273 -0
- data/rails_generators/lockdown/templates/app/controllers/permissions_controller.rb +22 -0
- data/rails_generators/lockdown/templates/app/controllers/sessions_controller.rb +39 -0
- data/rails_generators/lockdown/templates/app/controllers/user_groups_controller.rb +122 -0
- data/rails_generators/lockdown/templates/app/controllers/users_controller.rb +117 -0
- data/rails_generators/lockdown/templates/app/helpers/permissions_helper.rb +2 -0
- data/rails_generators/lockdown/templates/app/helpers/user_groups_helper.rb +2 -0
- data/rails_generators/lockdown/templates/app/helpers/users_helper.rb +2 -0
- data/rails_generators/lockdown/templates/app/models/permission.rb +13 -0
- data/rails_generators/lockdown/templates/app/models/profile.rb +10 -0
- data/rails_generators/lockdown/templates/app/models/user.rb +95 -0
- data/rails_generators/lockdown/templates/app/models/user_group.rb +15 -0
- data/rails_generators/lockdown/templates/app/views/permissions/index.html.erb +16 -0
- data/rails_generators/lockdown/templates/app/views/permissions/show.html.erb +26 -0
- data/rails_generators/lockdown/templates/app/views/sessions/new.html.erb +12 -0
- data/rails_generators/lockdown/templates/app/views/user_groups/edit.html.erb +33 -0
- data/rails_generators/lockdown/templates/app/views/user_groups/index.html.erb +20 -0
- data/rails_generators/lockdown/templates/app/views/user_groups/new.html.erb +31 -0
- data/rails_generators/lockdown/templates/app/views/user_groups/show.html.erb +29 -0
- data/rails_generators/lockdown/templates/app/views/users/edit.html.erb +51 -0
- data/rails_generators/lockdown/templates/app/views/users/index.html.erb +22 -0
- data/rails_generators/lockdown/templates/app/views/users/new.html.erb +50 -0
- data/rails_generators/lockdown/templates/app/views/users/show.html.erb +33 -0
- data/rails_generators/lockdown/templates/config/initializers/lockit.rb +1 -0
- data/rails_generators/lockdown/templates/db/migrate/create_admin_user.rb +17 -0
- data/rails_generators/lockdown/templates/db/migrate/create_permissions.rb +19 -0
- data/rails_generators/lockdown/templates/db/migrate/create_profiles.rb +26 -0
- data/rails_generators/lockdown/templates/db/migrate/create_user_groups.rb +19 -0
- data/rails_generators/lockdown/templates/db/migrate/create_users.rb +17 -0
- data/rails_generators/lockdown/templates/lib/lockdown/README +42 -0
- data/rails_generators/lockdown/templates/lib/lockdown/init.rb +122 -0
- data/spec/lockdown/database_spec.rb +158 -0
- data/spec/lockdown/frameworks/rails/controller_spec.rb +224 -0
- data/spec/lockdown/frameworks/rails/view_spec.rb +125 -0
- data/spec/lockdown/frameworks/rails_spec.rb +175 -0
- data/spec/lockdown/permission_spec.rb +156 -0
- data/spec/lockdown/rules_spec.rb +109 -0
- data/spec/lockdown/session_spec.rb +89 -0
- data/spec/lockdown/system_spec.rb +59 -0
- data/spec/lockdown_spec.rb +19 -0
- data/spec/rcov.opts +5 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +1 -0
- metadata +112 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "rails", "controller")
|
2
|
+
require File.join(File.dirname(__FILE__), "rails", "view")
|
3
|
+
|
4
|
+
module Lockdown
|
5
|
+
module Frameworks
|
6
|
+
module Rails
|
7
|
+
class << self
|
8
|
+
def use_me?
|
9
|
+
Object.const_defined?("ActionController") && ActionController.const_defined?("Base")
|
10
|
+
end
|
11
|
+
|
12
|
+
def included(mod)
|
13
|
+
mod.extend Lockdown::Frameworks::Rails::Environment
|
14
|
+
mixin
|
15
|
+
end
|
16
|
+
|
17
|
+
def mixin
|
18
|
+
Lockdown.controller_parent.class_eval do
|
19
|
+
include Lockdown::Session
|
20
|
+
include Lockdown::Frameworks::Rails::Controller::Lock
|
21
|
+
end
|
22
|
+
|
23
|
+
Lockdown.controller_parent.helper_method :authorized?
|
24
|
+
|
25
|
+
Lockdown.controller_parent.before_filter do |c|
|
26
|
+
c.set_current_user
|
27
|
+
c.configure_lockdown
|
28
|
+
c.check_request_authorization
|
29
|
+
end
|
30
|
+
|
31
|
+
Lockdown.controller_parent.filter_parameter_logging :password,
|
32
|
+
:password_confirmation
|
33
|
+
|
34
|
+
Lockdown.controller_parent.rescue_from SecurityError,
|
35
|
+
:with => proc{|e| access_denied(e)}
|
36
|
+
|
37
|
+
Lockdown.view_helper.class_eval do
|
38
|
+
include Lockdown::Frameworks::Rails::View
|
39
|
+
end
|
40
|
+
|
41
|
+
Lockdown::System.class_eval do
|
42
|
+
extend Lockdown::Frameworks::Rails::System
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end # class block
|
46
|
+
|
47
|
+
module Environment
|
48
|
+
|
49
|
+
def project_root
|
50
|
+
::RAILS_ROOT
|
51
|
+
end
|
52
|
+
|
53
|
+
def init_file
|
54
|
+
"#{project_root}/lib/lockdown/init.rb"
|
55
|
+
end
|
56
|
+
|
57
|
+
def controller_parent
|
58
|
+
ActionController::Base
|
59
|
+
end
|
60
|
+
|
61
|
+
def view_helper
|
62
|
+
ActionView::Base
|
63
|
+
end
|
64
|
+
|
65
|
+
def controller_class_name(str)
|
66
|
+
str = "#{str}Controller"
|
67
|
+
if str.include?("__")
|
68
|
+
str.split("__").collect{|p| Lockdown.camelize(p)}.join("::")
|
69
|
+
else
|
70
|
+
Lockdown.camelize(str)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module System
|
76
|
+
include Lockdown::Frameworks::Rails::Controller
|
77
|
+
|
78
|
+
def skip_sync?
|
79
|
+
Lockdown::System.fetch(:skip_db_sync_in).include?(ENV['RAILS_ENV'])
|
80
|
+
end
|
81
|
+
|
82
|
+
def load_controller_classes
|
83
|
+
@controller_classes = {}
|
84
|
+
|
85
|
+
maybe_load_framework_controller_parent
|
86
|
+
|
87
|
+
ApplicationController.helper_method :authorized?
|
88
|
+
|
89
|
+
ApplicationController.before_filter do |c|
|
90
|
+
c.set_current_user
|
91
|
+
c.configure_lockdown
|
92
|
+
c.check_request_authorization
|
93
|
+
end
|
94
|
+
|
95
|
+
ApplicationController.filter_parameter_logging :password,
|
96
|
+
:password_confirmation
|
97
|
+
|
98
|
+
ApplicationController.rescue_from SecurityError,
|
99
|
+
:with => proc{|e| access_denied(e)}
|
100
|
+
|
101
|
+
|
102
|
+
Dir.chdir("#{Lockdown.project_root}/app/controllers") do
|
103
|
+
Dir["**/*.rb"].sort.each do |c|
|
104
|
+
next if c == "application.rb"
|
105
|
+
lockdown_load(c)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if ENV['RAILS_ENV'] != 'production'
|
110
|
+
if ActiveSupport.const_defined?("Dependencies")
|
111
|
+
ActiveSupport::Dependencies.clear
|
112
|
+
else
|
113
|
+
Dependencies.clear
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def maybe_load_framework_controller_parent
|
119
|
+
if ::Rails::VERSION::MAJOR >= 2 && ::Rails::VERSION::MINOR >= 3
|
120
|
+
filename = "application_controller.rb"
|
121
|
+
else
|
122
|
+
filename = "application.rb"
|
123
|
+
end
|
124
|
+
|
125
|
+
require_or_load(filename)
|
126
|
+
end
|
127
|
+
|
128
|
+
def lockdown_load(filename)
|
129
|
+
klass = Lockdown.class_name_from_file(filename)
|
130
|
+
|
131
|
+
require_or_load(filename)
|
132
|
+
|
133
|
+
@controller_classes[klass] = Lockdown.qualified_const_get(klass)
|
134
|
+
end
|
135
|
+
|
136
|
+
def require_or_load(filename)
|
137
|
+
if ActiveSupport.const_defined?("Dependencies")
|
138
|
+
ActiveSupport::Dependencies.require_or_load(filename)
|
139
|
+
else
|
140
|
+
Dependencies.require_or_load(filename)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end # System
|
144
|
+
end # Rails
|
145
|
+
end # Frameworks
|
146
|
+
end # Lockdown
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Lockdown
|
2
|
+
module Frameworks
|
3
|
+
module Rails
|
4
|
+
module Controller
|
5
|
+
|
6
|
+
def available_actions(klass)
|
7
|
+
if klass.respond_to?(:action_methods)
|
8
|
+
klass.action_methods
|
9
|
+
else
|
10
|
+
klass.public_instance_methods - klass.hidden_actions
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def controller_name(klass)
|
15
|
+
klass.controller_name
|
16
|
+
end
|
17
|
+
|
18
|
+
# Locking methods
|
19
|
+
module Lock
|
20
|
+
def configure_lockdown
|
21
|
+
check_session_expiry
|
22
|
+
store_location
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_current_user
|
26
|
+
login_from_basic_auth? unless logged_in?
|
27
|
+
if logged_in?
|
28
|
+
Thread.current[:who_did_it] = Lockdown::System.
|
29
|
+
call(self, :who_did_it)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_request_authorization
|
34
|
+
unless authorized?(path_from_hash(params))
|
35
|
+
raise SecurityError, "Authorization failed for params #{params.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def path_allowed?(url)
|
40
|
+
session[:access_rights] ||= Lockdown::System.public_access
|
41
|
+
session[:access_rights].include?(url)
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_session_expiry
|
45
|
+
if session[:expiry_time] && session[:expiry_time] < Time.now
|
46
|
+
nil_lockdown_values
|
47
|
+
Lockdown::System.call(self, :session_timeout_method)
|
48
|
+
end
|
49
|
+
session[:expiry_time] = Time.now + Lockdown::System.fetch(:session_timeout)
|
50
|
+
end
|
51
|
+
|
52
|
+
def store_location
|
53
|
+
if (request.method == :get) && (session[:thispage] != sent_from_uri)
|
54
|
+
session[:prevpage] = session[:thispage] || ''
|
55
|
+
session[:thispage] = sent_from_uri
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def sent_from_uri
|
60
|
+
request.request_uri
|
61
|
+
end
|
62
|
+
|
63
|
+
def authorized?(url, method = nil)
|
64
|
+
return false unless url
|
65
|
+
|
66
|
+
return true if current_user_is_admin?
|
67
|
+
|
68
|
+
method ||= (params[:method] || request.method)
|
69
|
+
|
70
|
+
url_parts = URI::split(url.strip)
|
71
|
+
|
72
|
+
url = url_parts[5]
|
73
|
+
|
74
|
+
return true if path_allowed?(url)
|
75
|
+
|
76
|
+
begin
|
77
|
+
hash = ActionController::Routing::Routes.recognize_path(url, :method => method)
|
78
|
+
return path_allowed?(path_from_hash(hash)) if hash
|
79
|
+
rescue Exception
|
80
|
+
# continue on
|
81
|
+
end
|
82
|
+
|
83
|
+
# Passing in different domain
|
84
|
+
return remote_url?(url_parts[2])
|
85
|
+
end
|
86
|
+
|
87
|
+
def access_denied(e)
|
88
|
+
|
89
|
+
RAILS_DEFAULT_LOGGER.info "Access denied: #{e}"
|
90
|
+
|
91
|
+
if Lockdown::System.fetch(:logout_on_access_violation)
|
92
|
+
reset_session
|
93
|
+
end
|
94
|
+
respond_to do |format|
|
95
|
+
format.html do
|
96
|
+
store_location
|
97
|
+
redirect_to Lockdown::System.fetch(:access_denied_path)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
format.xml do
|
101
|
+
headers["Status"] = "Unauthorized"
|
102
|
+
headers["WWW-Authenticate"] = %(Basic realm="Web Password")
|
103
|
+
render :text => e.message, :status => "401 Unauthorized"
|
104
|
+
return
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def path_from_hash(hash)
|
110
|
+
hash[:controller].to_s + "/" + hash[:action].to_s
|
111
|
+
end
|
112
|
+
|
113
|
+
def remote_url?(domain = nil)
|
114
|
+
return false if domain.nil? || domain.strip.length == 0
|
115
|
+
request.host.downcase != domain.downcase
|
116
|
+
end
|
117
|
+
|
118
|
+
def redirect_back_or_default(default)
|
119
|
+
if session[:prevpage].nil? || session[:prevpage].blank?
|
120
|
+
redirect_to(default)
|
121
|
+
else
|
122
|
+
redirect_to(session[:prevpage])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Called from current_user. Now, attempt to login by
|
127
|
+
# basic authentication information.
|
128
|
+
def login_from_basic_auth?
|
129
|
+
username, passwd = get_auth_data
|
130
|
+
if username && passwd
|
131
|
+
set_session_user ::User.authenticate(username, passwd)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
@@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
|
136
|
+
# gets BASIC auth info
|
137
|
+
def get_auth_data
|
138
|
+
auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
|
139
|
+
auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
|
140
|
+
return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
|
141
|
+
end
|
142
|
+
end # Lock
|
143
|
+
end # Controller
|
144
|
+
end # Rails
|
145
|
+
end # Frameworks
|
146
|
+
end # Lockdown
|
147
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Lockdown
|
2
|
+
module Frameworks
|
3
|
+
module Rails
|
4
|
+
module View
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
alias_method :link_to_open, :link_to
|
8
|
+
alias_method :link_to, :link_to_secured
|
9
|
+
|
10
|
+
alias_method :button_to_open, :button_to
|
11
|
+
alias_method :button_to, :button_to_secured
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def link_to_secured(name, options = {}, html_options = nil)
|
16
|
+
url = url_for(options)
|
17
|
+
|
18
|
+
method = html_options ? html_options[:method] : nil
|
19
|
+
|
20
|
+
url_to_authorize = remove_subdirectory(url)
|
21
|
+
|
22
|
+
if authorized?(url_to_authorize, method)
|
23
|
+
return link_to_open(name, url, html_options)
|
24
|
+
end
|
25
|
+
return ""
|
26
|
+
end
|
27
|
+
|
28
|
+
def button_to_secured(name, options = {}, html_options = nil)
|
29
|
+
url = url_for(options)
|
30
|
+
|
31
|
+
method = html_options ? html_options[:method] : nil
|
32
|
+
|
33
|
+
url_to_authorize = remove_subdirectory(url)
|
34
|
+
|
35
|
+
if authorized?(url_to_authorize, method)
|
36
|
+
return button_to_open(name, url, html_options)
|
37
|
+
end
|
38
|
+
return ""
|
39
|
+
end
|
40
|
+
|
41
|
+
def link_to_or_show(name, options = {}, html_options = nil)
|
42
|
+
lnk = link_to(name, options, html_options)
|
43
|
+
lnk.length == 0 ? name : lnk
|
44
|
+
end
|
45
|
+
|
46
|
+
def links(*lis)
|
47
|
+
rvalue = []
|
48
|
+
lis.each{|link| rvalue << link if link.length > 0 }
|
49
|
+
rvalue.join( Lockdown::System.fetch(:link_separator) )
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def remove_subdirectory(url)
|
54
|
+
subdir = Lockdown::System.fetch(:subdirectory)
|
55
|
+
subdir ? url.gsub(/^\/?#{subdir}/,'') : url
|
56
|
+
end
|
57
|
+
|
58
|
+
end # View
|
59
|
+
end # Rails
|
60
|
+
end # Frameworks
|
61
|
+
end # Lockdown
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Lockdown
|
2
|
+
module Helper
|
3
|
+
def class_name_from_file(str)
|
4
|
+
str.split(".")[0].split("/").collect{|s| camelize(s) }.join("::")
|
5
|
+
end
|
6
|
+
|
7
|
+
# If str_sym is a Symbol (:users), return "Users"
|
8
|
+
# If str_sym is a String ("Users"), return :users
|
9
|
+
def convert_reference_name(str_sym)
|
10
|
+
if str_sym.is_a?(Symbol)
|
11
|
+
titleize(str_sym)
|
12
|
+
else
|
13
|
+
underscore(str_sym).tr(' ','_').to_sym
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_string(value)
|
18
|
+
if value.respond_to?(:name)
|
19
|
+
string_name(value.name)
|
20
|
+
else
|
21
|
+
string_name(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_symbol(value)
|
26
|
+
if value.respond_to?(:name)
|
27
|
+
symbol_name(value.name)
|
28
|
+
elsif value.is_a?(String)
|
29
|
+
symbol_name(value)
|
30
|
+
else
|
31
|
+
value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def camelize(str)
|
36
|
+
str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
37
|
+
end
|
38
|
+
|
39
|
+
def random_string(len = 10)
|
40
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
41
|
+
Array.new(len){||chars[rand(chars.size)]}.join
|
42
|
+
end
|
43
|
+
|
44
|
+
def administrator_group_string
|
45
|
+
string_name(administrator_group_symbol)
|
46
|
+
end
|
47
|
+
|
48
|
+
def administrator_group_symbol
|
49
|
+
:administrators
|
50
|
+
end
|
51
|
+
|
52
|
+
def qualified_const_defined?(klass)
|
53
|
+
if klass =~ /::/
|
54
|
+
namespace, klass = klass.split("::")
|
55
|
+
eval("#{namespace}.const_defined?(#{klass})") if const_defined?(namespace)
|
56
|
+
else
|
57
|
+
const_defined?(klass)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def qualified_const_get(klass)
|
62
|
+
if klass =~ /::/
|
63
|
+
namespace, klass = klass.split("::")
|
64
|
+
eval(namespace).const_get(klass)
|
65
|
+
else
|
66
|
+
const_get(klass)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def string_name(str_sym)
|
73
|
+
str_sym.is_a?(Symbol) ? convert_reference_name(str_sym) : str_sym
|
74
|
+
end
|
75
|
+
|
76
|
+
def symbol_name(str_sym)
|
77
|
+
str_sym.is_a?(String) ? convert_reference_name(str_sym) : str_sym
|
78
|
+
end
|
79
|
+
|
80
|
+
def titleize(str)
|
81
|
+
humanize(underscore(str)).gsub(/\b([a-z])/) { $1.capitalize }
|
82
|
+
end
|
83
|
+
|
84
|
+
def humanize(str)
|
85
|
+
str.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
86
|
+
end
|
87
|
+
|
88
|
+
def underscore(str)
|
89
|
+
str.to_s.gsub(/::/, '/').
|
90
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
91
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
92
|
+
tr("-", "_").downcase
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|