model_security_generator 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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' %>