lockdown_vail 1.6.2.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.
Files changed (67) hide show
  1. data/.gitignore +6 -0
  2. data/README.txt +36 -0
  3. data/Rakefile +38 -0
  4. data/VERSION +1 -0
  5. data/lib/lockdown/context.rb +41 -0
  6. data/lib/lockdown/database.rb +41 -0
  7. data/lib/lockdown/errors.rb +11 -0
  8. data/lib/lockdown/frameworks/rails/controller.rb +187 -0
  9. data/lib/lockdown/frameworks/rails/view.rb +50 -0
  10. data/lib/lockdown/frameworks/rails.rb +114 -0
  11. data/lib/lockdown/helper.rb +111 -0
  12. data/lib/lockdown/orms/active_record.rb +68 -0
  13. data/lib/lockdown/permission.rb +222 -0
  14. data/lib/lockdown/references.rb +19 -0
  15. data/lib/lockdown/rspec_helper.rb +114 -0
  16. data/lib/lockdown/rules.rb +372 -0
  17. data/lib/lockdown/session.rb +66 -0
  18. data/lib/lockdown/system.rb +58 -0
  19. data/lib/lockdown.rb +87 -0
  20. data/lockdown.gemspec +118 -0
  21. data/lockdown_vail.gemspec +120 -0
  22. data/rails_generators/lockdown/lockdown_generator.rb +274 -0
  23. data/rails_generators/lockdown/templates/app/controllers/permissions_controller.rb +22 -0
  24. data/rails_generators/lockdown/templates/app/controllers/sessions_controller.rb +39 -0
  25. data/rails_generators/lockdown/templates/app/controllers/user_groups_controller.rb +122 -0
  26. data/rails_generators/lockdown/templates/app/controllers/users_controller.rb +117 -0
  27. data/rails_generators/lockdown/templates/app/helpers/permissions_helper.rb +2 -0
  28. data/rails_generators/lockdown/templates/app/helpers/user_groups_helper.rb +2 -0
  29. data/rails_generators/lockdown/templates/app/helpers/users_helper.rb +2 -0
  30. data/rails_generators/lockdown/templates/app/models/permission.rb +13 -0
  31. data/rails_generators/lockdown/templates/app/models/profile.rb +10 -0
  32. data/rails_generators/lockdown/templates/app/models/user.rb +95 -0
  33. data/rails_generators/lockdown/templates/app/models/user_group.rb +15 -0
  34. data/rails_generators/lockdown/templates/app/views/permissions/index.html.erb +16 -0
  35. data/rails_generators/lockdown/templates/app/views/permissions/show.html.erb +26 -0
  36. data/rails_generators/lockdown/templates/app/views/sessions/new.html.erb +12 -0
  37. data/rails_generators/lockdown/templates/app/views/user_groups/edit.html.erb +33 -0
  38. data/rails_generators/lockdown/templates/app/views/user_groups/index.html.erb +20 -0
  39. data/rails_generators/lockdown/templates/app/views/user_groups/new.html.erb +31 -0
  40. data/rails_generators/lockdown/templates/app/views/user_groups/show.html.erb +29 -0
  41. data/rails_generators/lockdown/templates/app/views/users/edit.html.erb +51 -0
  42. data/rails_generators/lockdown/templates/app/views/users/index.html.erb +22 -0
  43. data/rails_generators/lockdown/templates/app/views/users/new.html.erb +50 -0
  44. data/rails_generators/lockdown/templates/app/views/users/show.html.erb +33 -0
  45. data/rails_generators/lockdown/templates/config/initializers/lockit.rb +1 -0
  46. data/rails_generators/lockdown/templates/db/migrate/create_admin_user.rb +17 -0
  47. data/rails_generators/lockdown/templates/db/migrate/create_permissions.rb +19 -0
  48. data/rails_generators/lockdown/templates/db/migrate/create_profiles.rb +26 -0
  49. data/rails_generators/lockdown/templates/db/migrate/create_user_groups.rb +19 -0
  50. data/rails_generators/lockdown/templates/db/migrate/create_users.rb +17 -0
  51. data/rails_generators/lockdown/templates/lib/lockdown/README +42 -0
  52. data/rails_generators/lockdown/templates/lib/lockdown/init.rb +136 -0
  53. data/spec/lockdown/context_spec.rb +191 -0
  54. data/spec/lockdown/database_spec.rb +66 -0
  55. data/spec/lockdown/frameworks/rails/controller_spec.rb +240 -0
  56. data/spec/lockdown/frameworks/rails/view_spec.rb +87 -0
  57. data/spec/lockdown/frameworks/rails_spec.rb +163 -0
  58. data/spec/lockdown/permission_spec.rb +156 -0
  59. data/spec/lockdown/rspec_helper_spec.rb +41 -0
  60. data/spec/lockdown/rules_spec.rb +245 -0
  61. data/spec/lockdown/session_spec.rb +125 -0
  62. data/spec/lockdown/system_spec.rb +51 -0
  63. data/spec/lockdown_spec.rb +19 -0
  64. data/spec/rcov.opts +5 -0
  65. data/spec/spec.opts +3 -0
  66. data/spec/spec_helper.rb +8 -0
  67. metadata +140 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.DS_Store
2
+ *.swp
3
+ pkg/**
4
+ doc/**
5
+ email.txt
6
+ coverage/**
data/README.txt ADDED
@@ -0,0 +1,36 @@
1
+ lockdown
2
+ by Andrew Stone
3
+ http://stonean.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ Lockdown is an authorization system for RubyOnRails (ver >= 2.1).
8
+
9
+ == INSTALL:
10
+
11
+ sudo gem install lockdown
12
+
13
+ == LICENSE:
14
+
15
+ (The MIT License)
16
+
17
+ Copyright (c) 2009 Andrew Stone
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining
20
+ a copy of this software and associated documentation files (the
21
+ 'Software'), to deal in the Software without restriction, including
22
+ without limitation the rights to use, copy, modify, merge, publish,
23
+ distribute, sublicense, and/or sell copies of the Software, and to
24
+ permit persons to whom the Software is furnished to do so, subject to
25
+ the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be
28
+ included in all copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
31
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
33
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
34
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
35
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
36
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rcov'
4
+ require 'spec/rake/spectask'
5
+
6
+ require 'lib/lockdown.rb'
7
+ task :default => 'rcov'
8
+
9
+ desc "Flog your code for Justice!"
10
+ task :flog do
11
+ sh('flog lib/**/*.rb')
12
+ end
13
+
14
+ desc "Run all specs and rcov in a non-sucky way"
15
+ Spec::Rake::SpecTask.new(:rcov) do |t|
16
+ t.spec_opts = IO.readlines("spec/spec.opts").map {|l| l.chomp.split " "}.flatten
17
+ t.spec_files = FileList['spec/**/*_spec.rb']
18
+ t.rcov = true
19
+ t.rcov_opts = IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
20
+ end
21
+
22
+ begin
23
+ require 'jeweler'
24
+ Jeweler::Tasks.new do |gemspec|
25
+ gemspec.name = "lockdown_vail"
26
+ gemspec.version = Lockdown.version
27
+ gemspec.rubyforge_project = "lockdown"
28
+ gemspec.summary = "Authorization system for Rails 2.x"
29
+ gemspec.description = "Restrict access to your controller actions. Supports basic model level restrictions as well"
30
+ gemspec.email = "andy@stonean.com"
31
+ gemspec.homepage = "http://stonean.com/wiki/lockdown"
32
+ gemspec.authors = ["Andrew Stone"]
33
+ gemspec.add_development_dependency('rspec')
34
+ end
35
+ Jeweler::GemcutterTasks.new
36
+ rescue LoadError
37
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
38
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.6.2
@@ -0,0 +1,41 @@
1
+ module Lockdown
2
+ class Context
3
+ attr_accessor :name, :allowed_methods
4
+
5
+ def to_s
6
+ self.class.to_s
7
+ end
8
+
9
+ def allows?(method_name)
10
+ @allowed_methods.include?(method_name)
11
+ end
12
+ end
13
+
14
+ class RootContext < Context
15
+ def initialize(name)
16
+ @name = name
17
+ @allowed_methods = %w(with_controller and_controller to_model)
18
+ end
19
+ end
20
+
21
+ class ControllerContext < Context
22
+ def initialize(name)
23
+ @name = name
24
+ @allowed_methods = %w(with_controller and_controller to_model only_methods except_methods)
25
+ end
26
+ end
27
+
28
+ class ModelContext < Context
29
+ def initialize(name)
30
+ @name = name
31
+ @allowed_methods = %w(where)
32
+ end
33
+ end
34
+
35
+ class ModelWhereContext < Context
36
+ def initialize(name)
37
+ @name = name
38
+ @allowed_methods = %w(is_in includes equals)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module Lockdown
2
+ class Database
3
+ class << self
4
+ # This is very basic and could be handled better using orm specific
5
+ # functionality, but I wanted to keep it generic to avoid creating
6
+ # an interface for each the different orm implementations.
7
+ # We'll see how it works...
8
+ def sync_with_db
9
+
10
+ @permissions = Lockdown::System.get_permissions
11
+ @user_groups = Lockdown::System.get_user_groups
12
+
13
+ unless Lockdown.user_group_class.table_exists?
14
+ Lockdown.logger.info ">> Lockdown tables not found. Skipping database sync."
15
+ return
16
+ end
17
+
18
+ maintain_user_groups
19
+ rescue Exception => e
20
+ Lockdown.logger.error ">> Lockdown sync failed: #{e.backtrace.join("\n")}"
21
+ end
22
+
23
+
24
+ def maintain_user_groups
25
+ # Create user groups not found in the database
26
+ @user_groups.each do |key|
27
+ str = Lockdown.get_string(key)
28
+ unless ug = Lockdown.user_group_class.find(:first, :conditions => ["name = ?", str])
29
+ create_user_group(str, key)
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_user_group(name_str, key)
35
+ Lockdown.logger.info ">> Lockdown: #{Lockdown::System.fetch(:user_group_model)} not in the db: #{name_str}, creating."
36
+ ug = Lockdown.user_group_class.create(:name => name_str)
37
+ end
38
+
39
+ end # class block
40
+ end # Database
41
+ end #Lockdown
@@ -0,0 +1,11 @@
1
+ module Lockdown
2
+ class InvalidRuleAssignment < StandardError; end
3
+
4
+ class InvalidRuleContext < StandardError; end
5
+
6
+ class PermissionScopeCollision < StandardError; end
7
+
8
+ class InvalidPermissionAssignment < StandardError; end
9
+
10
+ class GroupUndefinedError < StandardError; end
11
+ end
@@ -0,0 +1,187 @@
1
+ module Lockdown
2
+ module Frameworks
3
+ module Rails
4
+ module Controller
5
+
6
+ def available_actions(klass)
7
+ klass.action_methods
8
+ end
9
+
10
+ def controller_name(klass)
11
+ klass.controller_name
12
+ end
13
+
14
+ # Locking methods
15
+ module Lock
16
+
17
+ class LockdownSessionExpired < Exception
18
+ end
19
+
20
+ def configure_lockdown
21
+ Lockdown.maybe_parse_init
22
+ check_session_expiry
23
+ store_location
24
+ end
25
+
26
+ # Basic auth functionality needs to be reworked as
27
+ # Lockdown doesn't provide authentication functionality.
28
+ def set_current_user
29
+ login_from_basic_auth? unless logged_in?
30
+ if logged_in?
31
+ Thread.current[:who_did_it] = Lockdown::System.
32
+ call(self, :who_did_it)
33
+ end
34
+ end
35
+
36
+ def check_request_authorization
37
+ unless authorized?(path_from_hash(params))
38
+ raise SecurityError, "Authorization failed! \nparams: #{params.inspect}\nsession: #{session.inspect}"
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def path_allowed?(url)
45
+ access_rights = Lockdown::System.public_access + access_rights_from_session
46
+ access_rights.include?(url)
47
+ end
48
+
49
+ def check_session_expiry
50
+ if session[:expiry_time] && session[:expiry_time] < Time.now
51
+ nil_lockdown_values
52
+ Lockdown::System.call(self, :session_timeout_method)
53
+ raise LockdownSessionExpired, "Authorization failed! \nSession expired."
54
+ end
55
+ session[:expiry_time] = Time.now + Lockdown::System.fetch(:session_timeout)
56
+ end
57
+
58
+ def store_location
59
+ if (request.method == :get) && (session[:thispage] != sent_from_uri)
60
+ session[:prevpage] = session[:thispage] || ''
61
+ session[:thispage] = sent_from_uri
62
+ end
63
+ end
64
+
65
+ def sent_from_uri
66
+ request.request_uri
67
+ end
68
+
69
+ def authorized?(url, method = nil)
70
+ # Reset access unless caching?
71
+ add_lockdown_session_values unless Lockdown.caching?
72
+
73
+ return false unless url
74
+
75
+ return true if current_user_is_admin?
76
+
77
+ method ||= (params[:method] || request.method)
78
+
79
+ url_parts = URI::split(url.strip)
80
+
81
+ path = url_parts[5]
82
+
83
+ return true if path_allowed?(path)
84
+
85
+ begin
86
+ hash = ActionController::Routing::Routes.recognize_path(path, :method => method)
87
+ return path_allowed?(path_from_hash(hash)) if hash
88
+ rescue Exception => e
89
+ # continue on
90
+ end
91
+
92
+ # Mailto link
93
+ return true if url =~ /^mailto:/
94
+
95
+ # Public file
96
+ file = File.join(RAILS_ROOT, 'public', url)
97
+ return true if File.exists?(file)
98
+
99
+ # Passing in different domain
100
+ return remote_url?(url_parts[2])
101
+ end
102
+
103
+ def ld_access_denied(e)
104
+
105
+ Lockdown.logger.info "Access denied: #{e}"
106
+
107
+ if Lockdown::System.fetch(:logout_on_access_violation)
108
+ reset_session
109
+ end
110
+ respond_to do |format|
111
+ format.html do
112
+ store_location
113
+ redirect_to Lockdown::System.fetch(:access_denied_path)
114
+ return
115
+ end
116
+ format.xml do
117
+ headers["Status"] = "Unauthorized"
118
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
119
+ render :text => e.message, :status => "401 Unauthorized"
120
+ return
121
+ end
122
+ end
123
+ end
124
+
125
+ def session_expired(e)
126
+
127
+ Lockdown.logger.info "Access denied: #{e}"
128
+
129
+ if Lockdown::System.fetch(:logout_on_access_violation)
130
+ reset_session
131
+ end
132
+
133
+ respond_to do |format|
134
+ format.html do
135
+ store_location
136
+ session_timeout_path = Lockdown::System.fetch(:session_timeout_path) || Lockdown::System.fetch(:access_denied_path)
137
+ redirect_to session_timeout_path
138
+ return
139
+ end
140
+ format.xml do
141
+ headers["Status"] = "Unauthorized"
142
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
143
+ render :text => e.message, :status => "401 Unauthorized"
144
+ return
145
+ end
146
+ end
147
+ end
148
+
149
+ def path_from_hash(hash)
150
+ hash[:controller].to_s + "/" + hash[:action].to_s
151
+ end
152
+
153
+ def remote_url?(domain = nil)
154
+ return false if domain.nil? || domain.strip.length == 0
155
+ request.host.downcase != domain.downcase
156
+ end
157
+
158
+ def redirect_back_or_default(default)
159
+ if session[:prevpage].nil? || session[:prevpage].blank?
160
+ redirect_to(default)
161
+ else
162
+ redirect_to(session[:prevpage])
163
+ end
164
+ end
165
+
166
+ # Called from current_user. Now, attempt to login by
167
+ # basic authentication information.
168
+ def login_from_basic_auth?
169
+ username, passwd = get_auth_data
170
+ if username && passwd
171
+ set_session_user ::User.authenticate(username, passwd)
172
+ end
173
+ end
174
+
175
+ @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
176
+ # gets BASIC auth info
177
+ def get_auth_data
178
+ auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
179
+ auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
180
+ return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
181
+ end
182
+ end # Lock
183
+ end # Controller
184
+ end # Rails
185
+ end # Frameworks
186
+ end # Lockdown
187
+
@@ -0,0 +1,50 @@
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] : :get
19
+
20
+ if authorized?(url, method)
21
+ return link_to_open(name, url, html_options)
22
+ end
23
+ return ""
24
+ end
25
+
26
+ def button_to_secured(name, options = {}, html_options = nil)
27
+ url = url_for(options)
28
+
29
+ method = html_options ? html_options[:method] : :get
30
+
31
+ if authorized?(url, method)
32
+ return button_to_open(name, url, html_options)
33
+ end
34
+ return ""
35
+ end
36
+
37
+ def link_to_or_show(name, options = {}, html_options = nil)
38
+ lnk = link_to(name, options, html_options)
39
+ lnk.length == 0 ? name : lnk
40
+ end
41
+
42
+ def links(*lis)
43
+ rvalue = []
44
+ lis.each{|link| rvalue << link if link.length > 0 }
45
+ rvalue.join( Lockdown::System.fetch(:link_separator) )
46
+ end
47
+ end # View
48
+ end # Rails
49
+ end # Frameworks
50
+ end # Lockdown
@@ -0,0 +1,114 @@
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
+ mixin_controller
19
+
20
+ Lockdown.view_helper.class_eval do
21
+ include Lockdown::Frameworks::Rails::View
22
+ end
23
+
24
+ Lockdown.system.class_eval do
25
+ extend Lockdown::Frameworks::Rails::System
26
+ end
27
+ end
28
+
29
+ def mixin_controller(klass = Lockdown.controller_parent)
30
+ klass.class_eval do
31
+ include Lockdown::Session
32
+ include Lockdown::Frameworks::Rails::Controller::Lock
33
+ end
34
+
35
+ klass.helper_method :authorized?
36
+
37
+ klass.hide_action(:set_current_user, :configure_lockdown, :check_request_authorization, :check_model_authorization)
38
+
39
+ klass.before_filter do |c|
40
+ c.set_current_user
41
+ c.configure_lockdown
42
+ c.check_request_authorization
43
+ c.check_model_authorization
44
+ end
45
+
46
+ klass.filter_parameter_logging :password, :password_confirmation
47
+
48
+ klass.rescue_from SecurityError, :with => proc{|e| ld_access_denied(e)}
49
+ klass.rescue_from Lockdown::Frameworks::Rails::Controller::Lock::LockdownSessionExpired, :with => proc{|e| session_expired(e)}
50
+ end
51
+ end # class block
52
+
53
+ module Environment
54
+
55
+ def project_root
56
+ ::RAILS_ROOT
57
+ end
58
+
59
+ def init_file
60
+ "#{project_root}/lib/lockdown/init.rb"
61
+ end
62
+
63
+ def view_helper
64
+ ::ActionView::Base
65
+ end
66
+
67
+ # cache_classes is true in production and testing, need to
68
+ # modify the ApplicationController
69
+ def controller_parent
70
+ if caching?
71
+ ApplicationController
72
+ else
73
+ ActionController::Base
74
+ end
75
+ end
76
+
77
+ def caching?
78
+ ::Rails.configuration.cache_classes
79
+ end
80
+
81
+ # cache_classes is true in production and testing, need to
82
+ # do an instance eval instead
83
+ def add_controller_method(code)
84
+ Lockdown.controller_parent.class_eval code, __FILE__,__LINE__ +1
85
+ end
86
+
87
+ def controller_class_name(str)
88
+ str = "#{str}Controller"
89
+ if str.include?("__")
90
+ str.split("__").collect{|p| Lockdown.camelize(p)}.join("::")
91
+ else
92
+ Lockdown.camelize(str)
93
+ end
94
+ end
95
+
96
+ def fetch_controller_class(str)
97
+ eval("::#{controller_class_name(str)}")
98
+ end
99
+ end
100
+
101
+ module System
102
+ include Lockdown::Frameworks::Rails::Controller
103
+
104
+ def skip_sync?
105
+ Lockdown.system.fetch(:skip_db_sync_in).include?(framework_environment)
106
+ end
107
+
108
+ def framework_environment
109
+ ::Rails.env
110
+ end
111
+ end # System
112
+ end # Rails
113
+ end # Frameworks
114
+ end # Lockdown