model_security_generator 0.0.3 → 0.0.5

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.
@@ -43,26 +43,24 @@ class ModelSecurityGenerator < Rails::Generator::NamedBase
43
43
  m.file "views/#{action}.rhtml", "app/views/user/#{action}.rhtml"
44
44
  end
45
45
 
46
- # Partials
47
- m.directory "app/views/user"
48
- partial_views.each do |action|
49
- m.file "views/_#{action}.rhtml", "app/views/user/_#{action}.rhtml"
50
- end
51
-
52
46
  # Mailer
53
47
  m.directory "app/views/user_mailer"
54
48
  mailer_views.each do |action|
55
49
  m.file "mailer/#{action}.rhtml", "app/views/user_mailer/#{action}.rhtml"
56
50
  end
51
+
52
+ # Documentation
53
+ m.directory "manuals/ModelSecurity"
54
+ m.file "README", "manuals/ModelSecurity/README"
55
+ m.file "USAGE", "manuals/ModelSecurity/USAGE"
56
+ m.file "RUN_DEMO", "manuals/ModelSecurity/RUN_DEMO"
57
+ m.file "ADD_TO_APPLICATION_CONTROLLER",
58
+ "app/controllers/ADD_TO_APPLICATION_CONTROLLER.ModelSecurity"
57
59
  end
58
60
  end
59
61
 
60
62
  def user_views
61
- %w(activate admin_created created edit forgot_password_done list login login_admin logout new show success)
62
- end
63
-
64
- def partial_views
65
- %w(form)
63
+ %w(activate admin_created created forgot_password forgot_password_done login login_admin logout new success)
66
64
  end
67
65
 
68
66
  def mailer_views
@@ -0,0 +1,13 @@
1
+ # The filters added to this controller will be run for all controllers in the application.
2
+ # Likewise will all the methods added be available for all controllers.
3
+ #
4
+ # Added Facilities for User model (and thus also for Modal and ModelSecurity).
5
+ #
6
+ require 'user_support'
7
+
8
+ class ApplicationController < ActionController::Base
9
+ helper :ModelSecurity
10
+ include UserSupport
11
+
12
+ before_filter :user_setup
13
+ end
data/templates/README CHANGED
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,95 @@
1
+ = Running the Model Security Demo =
2
+
3
+ Here's how to run the model security demo. Once you've gone through this
4
+ process, you should be able
5
+
6
+ Generate a rails program called <i>test</i>, and change to its directory:
7
+
8
+ rails test
9
+ cd test
10
+
11
+ Install the model_security gem:
12
+
13
+ gem install model_security -
14
+
15
+ Have MySQL execute the script to create the database and a MySQL user ID for the rails program:
16
+
17
+ cd db/
18
+ mysql -u <i>MYSQL-ADMIN-NAME</i> -p < demo.sql
19
+
20
+ Examine demo.sql and users.sql . Note that demo.sql runs users.sql to define
21
+ the users table. When you include ModelSecurity in another Rails program,
22
+ you'll skip demo.sql and use users.sql to create the users table.
23
+
24
+ Edit the database configuration in db/database.yml to look like this:
25
+
26
+ development:
27
+ adapter: mysql
28
+ database: model_security_demo
29
+ host: localhost
30
+ username: m_s_demo
31
+ password:
32
+
33
+ Add some necessary facilities to your brand new application controller:
34
+
35
+ cd ../app/controllers
36
+ cp ADD_TO_APPLICATION_CONTROLLER.ModelSecurity application.rb
37
+ cd ../..
38
+
39
+ If this wasn't a new controller, you would edit application.rb to add the
40
+ facilities requested, rather than copying over the file.
41
+
42
+ Start the rails server:
43
+
44
+ script/server &
45
+
46
+ Open a web browser to http://localhost:3000/user/new
47
+ Use the form to create a new user. That user will automatically be
48
+ granted the administrator role, and will be activated immediately.
49
+
50
+ Subsequent users will <i>not</i> automatically get the administrator role,
51
+ although an administrator can grant it to them by editing their record
52
+ with /user/edit/<i>user-name</i> . Subsequent users will
53
+ not have their login activated immediately when they create it. Instead,
54
+ they will be sent an email containing a URL that they must use to activate
55
+ their login.
56
+
57
+ Now, you can play with the demo. The user controller responds to:
58
+
59
+ * /user/activate/<i>user-name</i>?token=<i>security-token</i>
60
+ Activate a user, after the user's login has been created.
61
+
62
+ * /user/destroy/<i>user-name</i>
63
+ Destroy a user. Requires the administrator role.
64
+
65
+ * /user/edit
66
+ Edit the attributes of a user. Administrators are allowed to edit more
67
+ fields than normal users, and normal are allowed to edit their own
68
+ record, not anyone else's.
69
+
70
+ * /user/forgot_password
71
+ Send an email to the user that facilitates password recovery.
72
+
73
+ * /user/list
74
+ List the users. Administrators can see more information than normal
75
+ users. Normal users can see some information on their own record
76
+ that they will not see in the records of other users.
77
+
78
+ * /user/login
79
+ Perform HTTP authentication to log in a user. If that doesn't work,
80
+ fall back on a login form.
81
+
82
+ * /user/logout
83
+ Log a user out. Actually loops back to the login action, because the only
84
+ way to get the browser to stop sending HTTP authentication data with
85
+ every request is to ask it to get new authentication data from the
86
+ user.
87
+
88
+ * /user/new
89
+ Create a new user.
90
+
91
+ * /user/show
92
+ Display information about a user. Administrators can see more information
93
+ than normal users.
94
+ Normal users can see some information on their own record
95
+ that they will not see in the records of other users.
data/templates/USAGE CHANGED
@@ -0,0 +1,2 @@
1
+
2
+
@@ -1,3 +1,7 @@
1
+ # Manipulate the User model.
2
+ #
3
+ # This (and the User model) should probably be merged with ActiveRBAC.
4
+ #
1
5
  # The HTTP authorization code is
2
6
  # derived from an example published by Maximillian Dornseif at
3
7
  # http://blogs.23.nu/c0re/stories/7409/
@@ -9,7 +13,8 @@
9
13
  # say that this is derived from the work of Joe Hosteny and Tobias Leutke.
10
14
  #
11
15
  class UserController < ApplicationController
12
- # helper ModelSecurity
16
+ scaffold :user
17
+ helper :ModelSecurity
13
18
 
14
19
  private
15
20
  # Require_admin will require an administrative login before the action
@@ -31,13 +36,6 @@ private
31
36
  render :status => 401
32
37
  end
33
38
 
34
- def initialize
35
- end
36
-
37
- public
38
- scaffold :user
39
-
40
- private
41
39
  public
42
40
 
43
41
  # Activate a new user, having logged in with a security token. All of the
@@ -45,28 +43,6 @@ public
45
43
  def activate
46
44
  end
47
45
 
48
- # Destroy a user object.
49
- def destroy
50
- user = User.find(@params[:id])
51
- user.destroy
52
- flash['notice'] = 'User destroyed.'
53
- redirect_to :action => :list
54
- end
55
-
56
- # Edit a user. Will only allow you to edit what your security clearance
57
- # allows, due to the magic of model security.
58
- def edit
59
- case @request.method
60
- when :get
61
- @user = User.find(@params[:id])
62
- when :post
63
- @user = User.find(@params[:id])
64
- @user.attributes = @params['user']
65
- @user.save
66
- redirect_to :action => :list
67
- end
68
- end
69
-
70
46
  # Send out a forgot-password email.
71
47
  def forgot_password
72
48
  case @request.method
@@ -89,19 +65,6 @@ public
89
65
  def forgot_password_done
90
66
  end
91
67
 
92
- # List users.
93
- # FIX: Get away from the scaffold here, and use the login instead of the ID
94
- # to access a user record. Using an ID gives outsiders a way to enumerate
95
- # the users, which has security implications.
96
- def list
97
- options = {
98
- :per_page => 10,
99
- :order_by => 'name, id'
100
- }
101
-
102
- @user_pages, @users = paginate(:user, options)
103
- end
104
-
105
68
  # Attempt HTTP authentication, and fall back on a login form.
106
69
  # If this method is called login_admin (it's an alias), keep trying
107
70
  # until an administrator logs in or the user pushes the "back" button.
@@ -150,6 +113,7 @@ public
150
113
  @user.activated = 1
151
114
  @user.save
152
115
  User.sign_on_by_session(1)
116
+ session[:user_id] = 1
153
117
  render :file => 'app/views/user/admin_created.rhtml'
154
118
  # Mail the user instructions on how to activate their account.
155
119
  else
@@ -169,14 +133,6 @@ public
169
133
  end
170
134
  end
171
135
 
172
- # Display a user's information.
173
- # FIX: Use the user's login instead of the record ID.
174
- # Using the record ID provides an easy way for outsiders to enumerate the
175
- # users, which has security implications.
176
- def show
177
- @user = User.find(@params[:id])
178
- end
179
-
180
136
  # Tell the user that an action succeeded.
181
137
  def success
182
138
  end
@@ -1,64 +1,128 @@
1
- module ActionView::Helpers::FormHelper
2
- private
1
+ require 'once'
3
2
 
4
- def self.restrict(name)
5
- module_eval <<-"end_eval", __FILE__, __LINE__
6
- alias old_#{name} #{name}
7
- def #{name}(object, method, options = {})
8
- restricted_input(:old_#{name}, object, method, options) \
9
- end
10
- end_eval
11
- end
3
+ module ModelSecurityHelper
4
+ end
12
5
 
13
- def restricted_input(old_name, object, method, options, *extra, &block)
14
- o = instance_variable_get("@" + object)
15
- writable = o.writable?(method)
16
- if o.readable?(method)
17
- if writable
18
- if block
19
- return yield(object, method, options, extra)
20
- else
21
- return send(old_name, object, method, options)
22
- end
23
- else
24
- return o.send(method)
25
- end
26
- elseif writable
27
- options[:value] = ''
28
- if block
29
- return yield(object, method, options, extra)
30
- else
31
- return send(old_name, object, method, options)
6
+ module ActiveRecord
7
+ class Base
8
+ once('@instanceAliasesDone') {
9
+ # Provides access to the pre-overload version of read_attribute, which
10
+ # is called by the overloaded version.
11
+ alias :old_read_attribute :read_attribute
12
+
13
+ # Provides access to the pre-overload version of write_attribute, which
14
+ # is called by the overloaded version.
15
+ alias :old_write_attribute :write_attribute
16
+ }
17
+
18
+ class << self
19
+ private
20
+ once('@classAliasesDone') {
21
+ # Provides access to the pre-overload version of content_columns, which
22
+ # is called by the overloaded version.
23
+ alias :old_content_columns :content_columns
24
+ }
25
+ public
26
+ # Overload the base method to understand the let_display directive
27
+ # of ModelSecurity. If display? is not true for a model attribute
28
+ # in this context, that attribute won't be reported as a content
29
+ # column.
30
+ def content_columns
31
+ old_content_columns.reject { |c|
32
+ not display?(c.name)
33
+ }
32
34
  end
33
35
  end
34
36
  end
37
+ end
35
38
 
36
- once ('@aliasesDone') { # Evaluating this twice would break it.
37
- alias old_check_box check_box
38
- alias old_radio_button radio_button
39
- }
40
-
41
- SimpleMethods = \
42
- ['file_field', 'hidden_field', 'password_field', 'text_area', 'text_field']
43
-
44
- public
39
+ module ActionView
40
+ module Helpers
41
+ # Overload the form helper functions to understand ModelSecurity and
42
+ # act upon the permissions that ModelSecurity encodes. If a model
43
+ # attribute does not have a read permission in the current context,
44
+ # its data won't be displayed, or read at all, and thus the helpers
45
+ # won't trigger a ModelSecurity exception. If there's no write
46
+ # permission on the datum, you'll display static data instead of an
47
+ # edit field. Write-only attributes work too. And all of this works
48
+ # on scaffolds.
49
+ #
50
+ # The methods overriden here are:
51
+ #
52
+ # * check_box
53
+ # * file_field
54
+ # * hidden_field
55
+ # * password_field
56
+ # * radio_button
57
+ # * text_area
58
+ # * text_field
59
+ #
60
+ module FormHelper
61
+ private
62
+
63
+ def self.restrict(name)
64
+ module_eval <<-"end_eval", __FILE__, __LINE__
65
+ private
66
+ alias :old_#{name} :#{name}
67
+ def #{name}(object, method, options = {})
68
+ restricted_input('#{name}', object, method, options)
69
+ end
70
+ end_eval
71
+ end
72
+
73
+ def restricted_input(function, object, method, options, *extra, &block)
74
+ o = instance_variable_get('@' + object)
45
75
 
46
- once ('@simpleMethodsDone') { # because it calls alias.
47
- SimpleMethods.each { | name | restrict(name) }
48
- }
76
+ if o.readable?(method)
77
+ if o.writable?(method) # Read+Write
78
+ if block
79
+ return yield(object, method, options, extra)
80
+ else
81
+ return send(('old_' + function).to_sym, object, method, options)
82
+ end
83
+ else # Read Only
84
+ return o.send(method)
85
+ end
86
+ else # Not Readable.
87
+ if o.writable?(method) # Write only.
88
+ u = { :value => '' }
49
89
 
50
- def check_box(object, method, options = {}, checked_value = "1", unchecked_value = "0")
51
- restricted_input(:old_check_box, object, method, options, checked_value, unchecked_value) {
52
- | object, method, options, extra |
53
- old_check_box(object, method, options, extra[0], extra[1])
54
- }
55
- end
90
+ return send(('old_' + function).to_sym, object, method, options.update(u))
91
+ else # Neither readable or writable.
92
+ return ''
93
+ end
94
+ end
95
+ end
96
+
97
+ # Evaluating the aliases twice would break them.
98
+ # Evaluating the constant definition twice causes a complaint.
99
+ once ('@aliasesDone') {
100
+ alias :old_check_box :check_box
101
+ alias :old_radio_button :radio_button
102
+ }
103
+
104
+
105
+ public
106
+
107
+ once ('@simpleMethodsDone') { # because it calls alias.
108
+ m = %w(file_field hidden_field password_field text_area text_field)
56
109
 
57
- def radio_button(object, method, tag_value, options = {})
58
- restricted_input(:old_radio_button, object, method, options, tag_value) {
59
- | object, method, options, extra |
60
- old_radio_button(object, method, extra[0], options)
61
- }
110
+ m.each { | name | restrict(name) }
111
+ }
112
+
113
+ def check_box(object, method, options = {}, checked_value = "1", unchecked_value = "0") #:nodoc:
114
+ restricted_input('check_box', object, method, options, checked_value, unchecked_value) {
115
+ | object, method, options, extra |
116
+ old_check_box(object, method, options, extra[0], extra[1])
117
+ }
118
+ end
119
+
120
+ def radio_button(object, method, tag_value, options = {}) #:nodoc:
121
+ restricted_input('radio_button', object, method, options, tag_value) {
122
+ | object, method, options, extra |
123
+ old_radio_button(object, method, extra[0], options)
124
+ }
125
+ end
126
+ end
62
127
  end
63
128
  end
64
-
@@ -1,3 +1,165 @@
1
+ require 'once'
2
+
3
+ module ModelSecurity
4
+ private
5
+ # Functions that are both class and instance methods within ModelSecurity.
6
+ module BothClassAndInstanceMethods
7
+ private
8
+ # Run a single test.
9
+ def run_test t
10
+ case t.class.name
11
+ when 'Proc'
12
+ return t.call(binding)
13
+ when 'Symbol'
14
+ return self.send(t)
15
+ when 'String'
16
+ return eval(t)
17
+ else
18
+ return false
19
+ end
20
+ end
21
+
22
+ # This does the permission test for readable?, writable?, and display?.
23
+ # A global variable is used to short-circuit recursion.
24
+ #
25
+ # FIX: The global variable should be replaced with a thread-local variable
26
+ # once I learn how to make one.
27
+ #
28
+ def run_tests(d, attribute)
29
+ global = d[:all]
30
+ local = d[attribute.to_sym]
31
+ result = true
32
+
33
+ if (global or local) and ($in_test_permission != true)
34
+ $in_test_permission = true
35
+ result = (run_test(global) or run_test(local))
36
+ $in_test_permission = false
37
+ end
38
+ return result
39
+ end
40
+
41
+ public
42
+ # Stub test for let_read and friends. Always returns true.
43
+ def always?
44
+ true
45
+ end
46
+
47
+ # Stub test for let_read and friends. Always returns false.
48
+ def never?
49
+ false
50
+ end
51
+ end
52
+ end
53
+
54
+ # Class methods for ModelSecurity. They are broken out this way so that they
55
+ # can be fed to base.extend(), Ruby interpreter magic so that class methods
56
+ # of this module work while a class including it is being defined.
57
+ # Uses a Rails-internal inheritable-attribute mechanism so that this data in a
58
+ # derived class survives modification of similar data in its base class.
59
+ #
60
+ module ModelSecurity
61
+ private
62
+ module ClassMethods
63
+ private
64
+ include BothClassAndInstanceMethods
65
+
66
+ # Internal function where the work of let_read, let_write, let_access,
67
+ # and let_display is done. Store the tests to be run for each attribute
68
+ # in the class, to be evaluated later. *permission* is :let_read,
69
+ # :let_write, or :let_display. *arguments* is a list of attributes
70
+ # upon which security permissions are being declared and a hash
71
+ # containing all options, currently just :if . *block*, if given,
72
+ # contains a security test.
73
+ #
74
+ def let(permission, arguments, block)
75
+ attributes = [] # List of attributes that this permission applies to.
76
+ parameters = {} # Optional parameters, currently only :if
77
+ procedure = nil # Permission-test procedure.
78
+
79
+ arguments.each { | argument |
80
+ case argument.class.name
81
+ when 'Hash'
82
+ parameters.merge! argument
83
+ else
84
+ attributes << argument
85
+ end
86
+ }
87
+ if not block.nil?
88
+ procedure = block
89
+ elsif (p = parameters[:if])
90
+ procedure = p
91
+ else
92
+ procedure = :always?
93
+ end
94
+
95
+ d = {}
96
+ attributes.each { | name | d[name] = procedure }
97
+ write_inheritable_hash(permission, d)
98
+ end
99
+
100
+ public
101
+
102
+ # Install default permissions for all of the attributes that Rails defines.
103
+ #
104
+ # Readable:
105
+ # created_at, created_on, type, id, updated_at, updated_on,
106
+ # lock_version, position, parent_id, lft, rgt,
107
+ # table_name + '_count'
108
+ #
109
+ # Writable:
110
+ # updated_at, updated_on, lock_version, position, parent_id, lft, rgt
111
+ #
112
+ # Writable only before the first save of an Active Record:
113
+ # created_at, created_on, type, id
114
+ #
115
+ def default_permissions
116
+ let_read :created_at, :created_on, :type, :id, :updated_at, \
117
+ :updated_on, :lock_version, :position, :parent_id, :lft, :rgt, \
118
+ (table_name + '_count').to_sym
119
+
120
+ # These shouldn't change after the first save.
121
+ let_write :created_at, :created_on, :type, :id, :if => :new_record?
122
+
123
+ # These can change.
124
+ let_write :updated_at, :updated_on, :lock_version, :position, :parent_id, \
125
+ :lft, :rgt
126
+ end
127
+
128
+ # Return true if display of the attribute is permitted. *attribute* is a
129
+ # symbol, and should match a field in the database schema corresponding to
130
+ # this model.
131
+ def display?(attribute)
132
+ if (d = read_inheritable_attribute(:let_display))
133
+ return run_tests(d, attribute)
134
+ else
135
+ return true
136
+ end
137
+ end
138
+
139
+ # Declare whether reads and writes are permitted on the named attributes.
140
+ def let_access(*arguments, &block)
141
+ let(:let_read, arguments, block)
142
+ let(:let_write, arguments, block)
143
+ end
144
+
145
+ # Declare whether display of the named attribute is permitted.
146
+ def let_display(*arguments, &block)
147
+ let(:let_display, arguments, block)
148
+ end
149
+
150
+
151
+ # Declare whether read is permitted upon the named attributes.
152
+ def let_read(*arguments, &block)
153
+ let(:let_read, arguments, block)
154
+ end
155
+
156
+ # Declare whether write is permitted upon the named attributes.
157
+ def let_write(*arguments, &block)
158
+ let(:let_write, arguments, block)
159
+ end
160
+ end
161
+ end
162
+
1
163
  # The ModelSecurity module allows you to specify security permissions on any
2
164
  # or all of the attributes of a model implemented using ActiveRecord.
3
165
  #
@@ -99,59 +261,7 @@
99
261
  # access is attempted.
100
262
  #
101
263
  module ModelSecurity
102
- end
103
-
104
- require 'once'
105
-
106
- module ModelSecurity::BothClassAndInstanceMethods
107
- private
108
- # Run a single test.
109
- def run_test t
110
- case t.class.name
111
- when 'Proc'
112
- return t.call(binding)
113
- when 'Symbol'
114
- return self.send(t)
115
- when 'String'
116
- return eval(t)
117
- else
118
- return false
119
- end
120
- end
121
-
122
- # This does the permission test for readable?, writable?, and display?.
123
- # A global variable is used to short-circuit recursion.
124
- #
125
- # FIX: The global variable should be replaced with a thread-local variable
126
- # once I learn how to make one.
127
- #
128
- def run_tests(d, attribute)
129
- global = d[:all]
130
- local = d[attribute.to_sym]
131
- result = true
132
-
133
- if (global or local) and ($in_test_permission != true)
134
- $in_test_permission = true
135
- result = (run_test(global) or run_test(local))
136
- $in_test_permission = false
137
- end
138
- return result
139
- end
140
-
141
- public
142
- # Stub test for let_read and friends. Always returns true.
143
- def always?
144
- true
145
- end
146
-
147
- # Stub test for let_read and friends. Always returns false.
148
- def never?
149
- false
150
- end
151
- end
152
-
153
- module ModelSecurity
154
- include ModelSecurity::BothClassAndInstanceMethods
264
+ include BothClassAndInstanceMethods
155
265
 
156
266
  # This does the permission test for readable? or writable?.
157
267
  def test_permission(permission, attribute)
@@ -241,121 +351,3 @@ public
241
351
  end
242
352
  end
243
353
 
244
- # Class methods for ModelSecurity. They are broken out this way so that they
245
- # can be fed to base.extend(), Ruby interpreter magic so that class methods
246
- # of this module work while a class including it is being defined.
247
- # Uses a Rails-internal inheritable-attribute mechanism so that this data in a
248
- # derived class survives modification of similar data in its base class.
249
- #
250
- module ModelSecurity::ClassMethods
251
- private
252
- include ModelSecurity::BothClassAndInstanceMethods
253
-
254
- # Internal function where the work of let_read, let_write, let_access,
255
- # and let_display is done. Store the tests to be run for each attribute
256
- # in the class, to be evaluated later. *permission* is :let_read,
257
- # :let_write, or :let_display. *arguments* is a list of attributes
258
- # upon which security permissions are being declared and a hash
259
- # containing all options, currently just :if . *block*, if given,
260
- # contains a security test.
261
- #
262
- def let(permission, arguments, block)
263
- attributes = [] # List of attributes that this permission applies to.
264
- parameters = {} # Optional parameters, currently only :if
265
- procedure = nil # Permission-test procedure.
266
-
267
- arguments.each { | argument |
268
- case argument.class.name
269
- when 'Hash'
270
- parameters.merge! argument
271
- else
272
- attributes << argument
273
- end
274
- }
275
- if not block.nil?
276
- procedure = block
277
- elsif (p = parameters[:if])
278
- procedure = p
279
- else
280
- procedure = :always?
281
- end
282
-
283
- d = {}
284
- attributes.each { | name | d[name] = procedure }
285
- write_inheritable_hash(permission, d)
286
- end
287
-
288
- public
289
-
290
- # Install default permissions for all of the attributes that Rails defines.
291
- #
292
- # Readable:
293
- # created_at, created_on, type, id, updated_at, updated_on,
294
- # lock_version, position, parent_id, lft, rgt,
295
- # table_name + '_count'
296
- #
297
- # Writable:
298
- # updated_at, updated_on, lock_version, position, parent_id, lft, rgt
299
- #
300
- # Writable only before the first save of an Active Record:
301
- # created_at, created_on, type, id
302
- #
303
- def default_permissions
304
- let_read :created_at, :created_on, :type, :id, :updated_at, \
305
- :updated_on, :lock_version, :position, :parent_id, :lft, :rgt, \
306
- (table_name + '_count').to_sym
307
-
308
- # These shouldn't change after the first save.
309
- let_write :created_at, :created_on, :type, :id, :if => :new_record?
310
-
311
- # These can change.
312
- let_write :updated_at, :updated_on, :lock_version, :position, :parent_id, \
313
- :lft, :rgt
314
- end
315
-
316
- # Return true if display of the attribute is permitted. *attribute* is a
317
- # symbol, and should match a field in the database schema corresponding to
318
- # this model.
319
- def display?(attribute)
320
- if (d = read_inheritable_attribute(:let_display))
321
- return run_tests(d, attribute)
322
- else
323
- return true
324
- end
325
- end
326
-
327
- # Declare whether reads and writes are permitted on the named attributes.
328
- def let_access(*arguments, &block)
329
- let(:let_read, arguments, block)
330
- let(:let_write, arguments, block)
331
- end
332
-
333
- # Declare whether display of the named attribute is permitted.
334
- def let_display(*arguments, &block)
335
- let(:let_display, arguments, block)
336
- end
337
-
338
-
339
- # Declare whether read is permitted upon the named attributes.
340
- def let_read(*arguments, &block)
341
- let(:let_read, arguments, block)
342
- end
343
-
344
- # Declare whether write is permitted upon the named attributes.
345
- def let_write(*arguments, &block)
346
- let(:let_write, arguments, block)
347
- end
348
- end
349
-
350
- class ActiveRecord::Base
351
- private
352
- once ('@aliasesDone') {
353
- # Provides access to the pre-overload version of read_attribute, which
354
- # is called by the overloaded version.
355
- alias old_read_attribute read_attribute
356
-
357
- # Provides access to the pre-overload version of write_attribute, which
358
- # is called by the overloaded version.
359
- alias old_write_attribute write_attribute
360
- }
361
- end
@@ -14,6 +14,8 @@ require 'user'
14
14
  require 'modal'
15
15
 
16
16
  module UserSupport
17
+ include Modal
18
+
17
19
  # Return true if the currently-logged-in user is the administrator.
18
20
  def admin?
19
21
  User.admin?
@@ -62,6 +64,10 @@ module UserSupport
62
64
  # that expects login information.
63
65
  #
64
66
  def user_setup
67
+ # require_* use Modal to return to what they were doing after HTTP
68
+ # authentication.
69
+ modal_setup
70
+
65
71
  # This is used by the logout action to discard the old HTTP authentiction.
66
72
  # Logout redirects to login and that generates a new authentication
67
73
  # request. That request is the only input that can tell the browser to
@@ -1,18 +1,12 @@
1
- <h1>Recover Your Login or Re-Set Your Password</h1>
1
+ Dear <%= @params['name'] %>,
2
2
 
3
- <p>
4
- Please enter the email address you used when you registered your login.
5
- A message will be sent to that email with the information you'll need to
6
- reset your password.
7
- </p>
8
- <%= start_form_tag :action => 'forgot_password' %>
9
- <table>
10
- <tr>
11
- <th><label for="user_email">Email</label></th>
12
- <td><%= text_field 'user', 'email' %></td>
13
- </tr>
14
- </table>
15
- <%= submit_tag "Send Email" %>
16
- <%= end_form_tag %>
3
+ To reset the password for your login
4
+ '<%= @params['login'] %>'
5
+ please access the following URL before
6
+ <%= @token_expiry %>:
17
7
 
18
- <%= link_to 'Back', :action => 'list' %>
8
+ <%= @url %>
9
+
10
+ Many Thanks
11
+
12
+ The Management
@@ -1,7 +1,7 @@
1
1
  Dear <%= @params['name'] %>,
2
2
 
3
3
  Your login
4
- '<%= @params['login' %>'
4
+ '<%= @params['login'] %>'
5
5
  has been created, but you must now activate it.
6
6
  To do so, please access the following URL before
7
7
  <%= @token_expiry %>:
@@ -92,6 +92,7 @@ private
92
92
 
93
93
  # These just control display.
94
94
  let_display :all, :if => :never?
95
+ let_display :admin, :activated, :if => :admin?
95
96
  let_display :login, :name, :email
96
97
 
97
98
  # These control both reading and writing.
@@ -115,7 +116,7 @@ private
115
116
  # Allow the very first user to be promoted to administrator.
116
117
  # Once there's an admin, that user has "let_access :all" and can
117
118
  # promote others to administrator.
118
- let_write :admin, :if => :initial_self_promotion
119
+ let_write :admin, :if => :initial_self_promotion?
119
120
 
120
121
  # If this is a new (never saved) record, or if this record corresponds to
121
122
  # the currently-logged-in user, allow reading of the email address.
@@ -137,12 +138,24 @@ private
137
138
  # unless this record is new (has never been saved).
138
139
  let_write :cypher, :email, :salt, :if => :new_or_me?
139
140
 
141
+
140
142
  # The security token can only be changed if we're the special "login" user.
141
- let_write :activated, :token, :token_expiry, :if => :logging_in?
143
+ let_write :activated, :password, :token, :token_expiry, :if => :logging_in?
142
144
 
143
145
  public
144
146
  attr_accessor :password, :password_confirmation, :old_password
145
147
 
148
+ # NOTE: :password, :password_confirmation, and :old_password
149
+ # are not attributes of the record, they are instance variables of the
150
+ # class and aren't written to disk under those names. But I declare them
151
+ # here because otherwise ModelSecurityHelper (which doesn't know that)
152
+ # isn't going to allow me to enter them into a form field.
153
+ #
154
+ # I like how fine-grained I can get.
155
+ let_write :password, :if => :new_or_me_or_logging_in?
156
+ let_write :password_confirmation, :if => :new_or_me?
157
+ let_write :old_password, :if => :me?
158
+
146
159
  # Change the user's password. Confirm the old password while doing so.
147
160
  def change_password(attributes)
148
161
  @password_is_new = true
@@ -191,7 +204,7 @@ public
191
204
  # himself to administrator. This is used to bootstrap the first administrator
192
205
  # and for no other purpose.
193
206
  def initial_self_promotion?
194
- return (self == User.current and id == 1 and not admin?)
207
+ return ((id == 1) and (not admin?) and (self.class.count == 1))
195
208
  end
196
209
 
197
210
  # Return true if the user is currently logging in. This security test allows
@@ -0,0 +1,18 @@
1
+ <h1>Recover Your Login or Re-Set Your Password</h1>
2
+
3
+ <p>
4
+ Please enter the email address you used when you registered your login.
5
+ A message will be sent to that email with the information you'll need to
6
+ reset your password.
7
+ </p>
8
+ <%= start_form_tag :action => 'forgot_password' %>
9
+ <table>
10
+ <tr>
11
+ <th><label for="user_email">Email</label></th>
12
+ <td><%= text_field 'user', 'email' %></td>
13
+ </tr>
14
+ </table>
15
+ <%= submit_tag "Send Email" %>
16
+ <%= end_form_tag %>
17
+
18
+ <%= link_to 'Back', :action => 'list' %>
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.4
3
3
  specification_version: 1
4
4
  name: model_security_generator
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.3
7
- date: 2005-09-23
6
+ version: 0.0.5
7
+ date: 2005-10-04
8
8
  summary: "[Rails] Model security and authentication generator."
9
9
  require_paths:
10
10
  - "."
@@ -15,7 +15,7 @@ description: Generates Rails code implementing a model security and authenticati
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
- has_rdoc: false
18
+ has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
21
  -
@@ -31,6 +31,8 @@ files:
31
31
  - model_security_generator.rb
32
32
  - templates/README
33
33
  - templates/USAGE
34
+ - templates/RUN_DEMO
35
+ - templates/ADD_TO_APPLICATION_CONTROLLER
34
36
  - templates/controllers/user_controller.rb
35
37
  - templates/db/demo.sql
36
38
  - templates/db/users.sql
@@ -49,18 +51,15 @@ files:
49
51
  - templates/test/user_controller_test.rb
50
52
  - templates/test/user_test.rb
51
53
  - templates/test/users.yml
52
- - templates/views/_form.rhtml
53
54
  - templates/views/activate.rhtml
54
55
  - templates/views/admin_created.rhtml
55
56
  - templates/views/created.rhtml
56
- - templates/views/edit.rhtml
57
+ - templates/views/forgot_password.rhtml
57
58
  - templates/views/forgot_password_done.rhtml
58
- - templates/views/list.rhtml
59
59
  - templates/views/login.rhtml
60
60
  - templates/views/login_admin.rhtml
61
61
  - templates/views/logout.rhtml
62
62
  - templates/views/new.rhtml
63
- - templates/views/show.rhtml
64
63
  - templates/views/success.rhtml
65
64
  test_files: []
66
65
  rdoc_options: []
@@ -1,27 +0,0 @@
1
- <%= error_messages_for 'user' %>
2
-
3
- <!--[form:user]-->
4
- <table>
5
- <% for column in User.content_columns %>
6
- <% if User.display?(column.name) %>
7
- <tr>
8
- <th>
9
- <label for="user_#{column.name}">
10
- <%= column.human_name %>:
11
- </label>
12
- </th>
13
- <td>
14
- <% if column.name.index('password') %>
15
- <%= password_field('user', column.name) %>
16
- <% else %>
17
- <%= text_field('user', column.name) %>
18
- <% end %>
19
- </td>
20
- </tr>
21
- <% end %>
22
- <% end %>
23
-
24
- </table>
25
-
26
- <!--[eoform:user]-->
27
-
@@ -1,10 +0,0 @@
1
- <h1>Editing user</h1>
2
-
3
- <% error_messages_for 'user' %>
4
- <%= start_form_tag :action => 'edit', :id => @user %>
5
- <%= render_partial 'form' %>
6
- <%= submit_tag 'Edit' %>
7
- <%= end_form_tag %>
8
-
9
- <%= link_to 'Show', :action => 'show', :id => @user %> |
10
- <%= link_to 'Back', :action => 'list' %>
@@ -1,35 +0,0 @@
1
- <h1>Listing users</h1>
2
-
3
- <table>
4
- <tr>
5
- <% for column in User.content_columns %>
6
- <% if User.display?(column.name) %>
7
- <th><%= column.human_name %></th>
8
- <% end %>
9
- <% end %>
10
- </tr>
11
-
12
- <% for user in @users %>
13
- <tr>
14
- <% for column in User.content_columns %>
15
- <% if User.display?(column.name) %>
16
- <% if user.readable?(column.name) %>
17
- <td><%= user.send(column.name) %></td>
18
- <% else %>
19
- <td></td>
20
- <% end %>
21
- <% end %>
22
- <% end %>
23
- <td><%= link_to 'Show', :action => 'show', :id => user %></td>
24
- <td><%= link_to 'Edit', :action => 'edit', :id => user %></td>
25
- <td><%= link_to 'Destroy', {:action => 'destroy', :id => user}, :confirm => 'Are you sure?' %></td>
26
- </tr>
27
- <% end %>
28
- </table>
29
-
30
- <%= link_to 'Previous page', { :page => @user_pages.current.previous } if @user_pages.current.previous %>
31
- <%= link_to 'Next page', { :page => @user_pages.current.next } if @user_pages.current.next %>
32
-
33
- <br />
34
-
35
- <%= link_to 'New user', :action => 'new' %>
@@ -1,10 +0,0 @@
1
- <% for column in User.content_columns %>
2
- <% if User.display?(column.name) and @user.readable?(column.name) %>
3
- <p>
4
- <b><%= column.human_name %>:</b> <%=h @user.send(column.name) %>
5
- </p>
6
- <% end %>
7
- <% end %>
8
-
9
- <%= link_to 'Edit', :action => 'edit', :id => @user %> |
10
- <%= link_to 'Back', :action => 'list' %>