model_security_generator 0.0.7 → 0.0.8
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/model_security_generator.rb +5 -1
- data/templates/Tutorial.html +669 -0
- data/templates/controllers/user_controller.rb +131 -15
- data/templates/db/demo.sql +1 -1
- data/templates/db/schema.sql +2 -0
- data/templates/db/user_configurations.sql +7 -0
- data/templates/helpers/model_security_helper.rb +16 -3
- data/templates/lib/modal.rb +9 -6
- data/templates/models/user_configuration.rb +18 -0
- data/templates/models/user_mailer.rb +10 -4
- data/templates/views/configure.rhtml +26 -0
- data/templates/views/list.rhtml +28 -0
- metadata +8 -3
- data/templates/views/login_admin.rhtml +0 -16
data/model_security_generator.rb
CHANGED
|
@@ -18,6 +18,7 @@ class ModelSecurityGenerator < Rails::Generator::NamedBase
|
|
|
18
18
|
|
|
19
19
|
# User
|
|
20
20
|
m.file "models/user.rb", "app/models/user.rb"
|
|
21
|
+
m.file "models/user_configuration.rb", "app/models/user_configuration.rb"
|
|
21
22
|
m.file "controllers/user_controller.rb", "app/controllers/user_controller.rb"
|
|
22
23
|
|
|
23
24
|
# User mailer
|
|
@@ -33,7 +34,9 @@ class ModelSecurityGenerator < Rails::Generator::NamedBase
|
|
|
33
34
|
|
|
34
35
|
# Schemas, configuration and miscellaneous
|
|
35
36
|
m.file "db/demo.sql", "db/demo.sql"
|
|
37
|
+
m.file "db/schema.sql", "db/schema.sql"
|
|
36
38
|
m.file "db/users.sql", "db/users.sql"
|
|
39
|
+
m.file "db/user_configurations.sql", "db/user_configurations.sql"
|
|
37
40
|
|
|
38
41
|
# Layout and stylesheet.
|
|
39
42
|
|
|
@@ -54,13 +57,14 @@ class ModelSecurityGenerator < Rails::Generator::NamedBase
|
|
|
54
57
|
m.file "README", "manuals/ModelSecurity/README"
|
|
55
58
|
m.file "USAGE", "manuals/ModelSecurity/USAGE"
|
|
56
59
|
m.file "RUN_DEMO", "manuals/ModelSecurity/RUN_DEMO"
|
|
60
|
+
m.file "Tutorial.html", "manuals/ModelSecurity/Tutorial.html"
|
|
57
61
|
m.file "ADD_TO_APPLICATION_CONTROLLER",
|
|
58
62
|
"app/controllers/ADD_TO_APPLICATION_CONTROLLER.ModelSecurity"
|
|
59
63
|
end
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
def user_views
|
|
63
|
-
%w(activate admin_created created forgot_password forgot_password_done login
|
|
67
|
+
%w(activate admin_created configure created forgot_password forgot_password_done list login logout new success)
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
def mailer_views
|
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
3
|
+
|
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
5
|
+
<head>
|
|
6
|
+
<meta name="generator" content=
|
|
7
|
+
"HTML Tidy for Linux/x86 (vers 12 April 2005), see www.w3.org" />
|
|
8
|
+
|
|
9
|
+
<title>Securing your Rails models with ModelSecurity</title>
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body>
|
|
13
|
+
<h1>Securing your Rails models with ModelSecurity.</h1><i>Bruce
|
|
14
|
+
Perens<br />
|
|
15
|
+
<a href="mailto:bruce@perens.com">bruce@perens.com</a><br />
|
|
16
|
+
Vice President, Sourcelabs.</i><br />
|
|
17
|
+
Last modified: Wed Oct 5 14:26:14 PDT 2005<br />
|
|
18
|
+
|
|
19
|
+
<p><b>ModelSecurity</b> helps Ruby on Rails developers implement
|
|
20
|
+
a security <i>defense in depth</i> by implementing access control
|
|
21
|
+
within the data model.</p>
|
|
22
|
+
|
|
23
|
+
<p>If you are like most developers, you think about security when
|
|
24
|
+
you program controllers and views. But a bug in your controller
|
|
25
|
+
or view can compromise the security of your application, unless
|
|
26
|
+
your data model has <i>also</i> been secured.</p>
|
|
27
|
+
|
|
28
|
+
<p>The economical, flexible, and extremely readable means of
|
|
29
|
+
specifying access controls provided by ModelSecurity makes it
|
|
30
|
+
easier for the developer to <i>think</i> about security, and
|
|
31
|
+
makes security assumptions that might otherwise live in one
|
|
32
|
+
developers head <i>concrete</i> and <i>communicable to
|
|
33
|
+
others.</i></p>
|
|
34
|
+
|
|
35
|
+
<p><i>Defense in depth</i> is a common military strategy to
|
|
36
|
+
prevent an attack from succeeding. If the attacker gets through
|
|
37
|
+
the first level of defense, there's another level there to meet
|
|
38
|
+
him. Defense in depth is useful to programmers because it will
|
|
39
|
+
reveal a flaw for repair in the first level while simultaneously
|
|
40
|
+
stopping the attack at the second level. Most developers
|
|
41
|
+
implement some security features in their controllers and views:
|
|
42
|
+
for example, a controller will generally not allow access to
|
|
43
|
+
administrative features unless the user is logged in as an
|
|
44
|
+
administrator, and your views should not display data that is
|
|
45
|
+
inappropriate for a particular user. This is the "first layer of
|
|
46
|
+
defense". At that point, we come to the data model access
|
|
47
|
+
permissions: your second layer of defense. If you've specified
|
|
48
|
+
them, ActiveRecord will not allow the data to be revealed or
|
|
49
|
+
modified in an inappropriate manner. Views will in general
|
|
50
|
+
quietly fail to present non-permitted material in a way that can
|
|
51
|
+
be exploited: it will be blank if it's not readable, and will be
|
|
52
|
+
presented as static data rather than in an edit field if it's not
|
|
53
|
+
writable. This behavior facilitates scaffolding, which would
|
|
54
|
+
display and allow modification of everything if not told
|
|
55
|
+
otherwise. A non-permitted controller access to the data model
|
|
56
|
+
will result in a <code>SecurityViolation</code> exception being
|
|
57
|
+
raised, which causes the action to terminate and logs diagnostic
|
|
58
|
+
information that will assist the developer to locate the
|
|
59
|
+
controller bug.</p>
|
|
60
|
+
|
|
61
|
+
<p>ModelSecurity adds these facilities to Rails:</p>
|
|
62
|
+
|
|
63
|
+
<ul>
|
|
64
|
+
<li><code>let_read,</code> <code>let_write</code>, and
|
|
65
|
+
<code>let_access</code> are added to model declarations. They
|
|
66
|
+
allow fine-grained security permissions to be specified for
|
|
67
|
+
each model attribute, or for all of them at once.</li>
|
|
68
|
+
|
|
69
|
+
<li>Security conditions are specified as blocks or the names of
|
|
70
|
+
functions. Each returns a boolean, and <code>true</code>
|
|
71
|
+
indicates that the access should proceed while
|
|
72
|
+
<code>false</code> causes it to be denied. This mechanism
|
|
73
|
+
allows any user authorization system to be wired into
|
|
74
|
+
ModelSecurity with only a few statements.</li>
|
|
75
|
+
|
|
76
|
+
<li>The facilities that are most commonly used to write Rails
|
|
77
|
+
views, forms, and scaffolds heed the data model security
|
|
78
|
+
settings. They will not present or allow the modification of an
|
|
79
|
+
ActiveRecord attribute if that action is prohibited by the
|
|
80
|
+
access controls on the data model.</li>
|
|
81
|
+
|
|
82
|
+
<li>ModelSecurity provides a <code>let_display</code> facility
|
|
83
|
+
that works similarly to the security specifications, and tells
|
|
84
|
+
Rails scaffolds and views what fields of a data model are
|
|
85
|
+
"interesting" and what fields need not be shown to a particular
|
|
86
|
+
user.</li>
|
|
87
|
+
|
|
88
|
+
<li>A simple <b>User</b> model with a login facility, already
|
|
89
|
+
configured to work with ModelSecurity, is part of the
|
|
90
|
+
package.</li>
|
|
91
|
+
|
|
92
|
+
<li>The <b>UserSupport</b> controller mixin provides a login
|
|
93
|
+
facility with HTTP authentication to the entire application:
|
|
94
|
+
|
|
95
|
+
<ul>
|
|
96
|
+
<li><code>User_setup</code> is a before-filter for the
|
|
97
|
+
entire application. It handles login control and session
|
|
98
|
+
management without additional attention from the
|
|
99
|
+
programmer.</li>
|
|
100
|
+
|
|
101
|
+
<li>The before-filters <code>require_login</code> and
|
|
102
|
+
<code>require_admin</code> ensure that a user logs in with
|
|
103
|
+
proper privilege and then will complete the requested
|
|
104
|
+
action.</li>
|
|
105
|
+
</ul>
|
|
106
|
+
</li>
|
|
107
|
+
|
|
108
|
+
<li>
|
|
109
|
+
<b>Modal</b>, a means to handle two common interactions in
|
|
110
|
+
web design:
|
|
111
|
+
|
|
112
|
+
<ul>
|
|
113
|
+
<li>Modal web forms such as login dialogues that
|
|
114
|
+
"interrupt" an action and then allow it to complete after
|
|
115
|
+
the form has been submitted.</li>
|
|
116
|
+
|
|
117
|
+
<li>Modal forms that are called from a page to which they
|
|
118
|
+
will later return the user, such as comment forms within a
|
|
119
|
+
weblog.</li>
|
|
120
|
+
</ul>
|
|
121
|
+
</li>
|
|
122
|
+
</ul>
|
|
123
|
+
|
|
124
|
+
<h2>Getting Started</h2>
|
|
125
|
+
|
|
126
|
+
<p>Before we go through the features of ModelSecuity, it's useful
|
|
127
|
+
to get working code on your system. To go through this demo,
|
|
128
|
+
you'll need to have Rails, RubyGems, and MySQL installed.</p>
|
|
129
|
+
|
|
130
|
+
<p>Generate a rails program called <i>test</i>, and change to its
|
|
131
|
+
directory:</p>
|
|
132
|
+
|
|
133
|
+
<blockquote>
|
|
134
|
+
<pre>
|
|
135
|
+
rails test
|
|
136
|
+
cd test
|
|
137
|
+
</pre>
|
|
138
|
+
</blockquote>
|
|
139
|
+
|
|
140
|
+
<p>Install the model_security gem:</p>
|
|
141
|
+
|
|
142
|
+
<blockquote>
|
|
143
|
+
<pre>
|
|
144
|
+
gem install model_security_generator
|
|
145
|
+
|
|
146
|
+
</pre>
|
|
147
|
+
</blockquote>
|
|
148
|
+
|
|
149
|
+
<p>Generate model_security for your application</p>
|
|
150
|
+
|
|
151
|
+
<blockquote>
|
|
152
|
+
<pre>
|
|
153
|
+
script/generate model_security -
|
|
154
|
+
|
|
155
|
+
</pre>
|
|
156
|
+
</blockquote>
|
|
157
|
+
|
|
158
|
+
<p>Have MySQL execute the script to create the database and a
|
|
159
|
+
MySQL user ID for the rails program:</p>
|
|
160
|
+
|
|
161
|
+
<blockquote>
|
|
162
|
+
<pre>
|
|
163
|
+
cd db/
|
|
164
|
+
mysql -u <i>MYSQL-ADMIN-NAME</i> -p < demo.sql
|
|
165
|
+
</pre>
|
|
166
|
+
</blockquote>
|
|
167
|
+
|
|
168
|
+
<p>Examine demo.sql and users.sql . users.sql defines the table
|
|
169
|
+
of users. An accessory table defined by user_configurations.sql
|
|
170
|
+
is used to hold configuration choices for the user system. When
|
|
171
|
+
you include ModelSecurity in another Rails program, you'll skip
|
|
172
|
+
demo.sql and use users.sql and user_configurations.sql to create
|
|
173
|
+
their tables.</p>
|
|
174
|
+
|
|
175
|
+
<p>Edit the database configuration in db/database.yml to look
|
|
176
|
+
like this:</p>
|
|
177
|
+
|
|
178
|
+
<blockquote>
|
|
179
|
+
<pre>
|
|
180
|
+
development:
|
|
181
|
+
adapter: mysql
|
|
182
|
+
database: model_security_demo
|
|
183
|
+
host: localhost
|
|
184
|
+
username: m_s_demo
|
|
185
|
+
password:
|
|
186
|
+
</pre>
|
|
187
|
+
</blockquote>
|
|
188
|
+
|
|
189
|
+
<p>Add some necessary facilities to your brand new application
|
|
190
|
+
controller:</p>
|
|
191
|
+
|
|
192
|
+
<blockquote>
|
|
193
|
+
<pre>
|
|
194
|
+
cd ../app/controllers
|
|
195
|
+
cp ADD_TO_APPLICATION_CONTROLLER.ModelSecurity application.rb
|
|
196
|
+
cd ../..
|
|
197
|
+
</pre>
|
|
198
|
+
</blockquote>
|
|
199
|
+
|
|
200
|
+
<p>If this wasn't a new controller, you would edit application.rb
|
|
201
|
+
to add the facilities requested, rather than copying over the
|
|
202
|
+
file.</p>
|
|
203
|
+
|
|
204
|
+
<p>Start the rails server:</p>
|
|
205
|
+
|
|
206
|
+
<blockquote>
|
|
207
|
+
<pre>
|
|
208
|
+
script/server &
|
|
209
|
+
</pre>
|
|
210
|
+
</blockquote>
|
|
211
|
+
|
|
212
|
+
<p>Open a web browser to http://localhost:3000/user/new Use the
|
|
213
|
+
form to create a new user. That user will automatically be
|
|
214
|
+
granted the administrator role, and will be activated
|
|
215
|
+
immediately.</p>
|
|
216
|
+
|
|
217
|
+
<p>Subsequent users will <i>not</i> automatically get the
|
|
218
|
+
administrator role, although an administrator can grant it to
|
|
219
|
+
them by editing their record with /user/edit/<i>user-name</i> .
|
|
220
|
+
Subsequent users will not have their login activated immediately
|
|
221
|
+
when they create it. Instead, they will be sent an email
|
|
222
|
+
containing a URL that they must use to activate their login.</p>
|
|
223
|
+
|
|
224
|
+
<p>Now, you can play with the demo. The user controller responds
|
|
225
|
+
to:</p>
|
|
226
|
+
|
|
227
|
+
<dl>
|
|
228
|
+
<dt>
|
|
229
|
+
<code>/user/activate/<i>id</i>?token=<i>security-token</i></code></dt>
|
|
230
|
+
|
|
231
|
+
<dd>Activate a user, after the user's login has been
|
|
232
|
+
created.</dd>
|
|
233
|
+
|
|
234
|
+
<dt><code>/user/configure/<i>id</i></code></dt>
|
|
235
|
+
|
|
236
|
+
<dd>
|
|
237
|
+
Configure the User system. Currently allows you to choose:
|
|
238
|
+
|
|
239
|
+
<ul>
|
|
240
|
+
<li>Whether new users must go through email confirmation
|
|
241
|
+
before they can log in.</li>
|
|
242
|
+
|
|
243
|
+
<li>What the sender address is for emails generated by this
|
|
244
|
+
software.</li>
|
|
245
|
+
</ul>The configure function requires the administrator role.
|
|
246
|
+
</dd>
|
|
247
|
+
|
|
248
|
+
<dt><code>/user/destroy/<i>id</i></code></dt>
|
|
249
|
+
|
|
250
|
+
<dd>Destroy a user. Requires the administrator role.</dd>
|
|
251
|
+
|
|
252
|
+
<dt><code>/user/edit/<i>id</i></code></dt>
|
|
253
|
+
|
|
254
|
+
<dd>Edit the attributes of a user. Administrators are allowed
|
|
255
|
+
to edit more fields than normal users, and normal are allowed
|
|
256
|
+
to edit their own record, not anyone else's.</dd>
|
|
257
|
+
|
|
258
|
+
<dt><code>/user/forgot_password</code></dt>
|
|
259
|
+
|
|
260
|
+
<dd>Send an email to the user that facilitates password
|
|
261
|
+
recovery.</dd>
|
|
262
|
+
|
|
263
|
+
<dt><code>/user/list</code></dt>
|
|
264
|
+
|
|
265
|
+
<dd>List the users. Administrators can see more information
|
|
266
|
+
than normal users. Normal users can see some information on
|
|
267
|
+
their own record that they will not see in the records of other
|
|
268
|
+
users.</dd>
|
|
269
|
+
|
|
270
|
+
<dt><code>/user/login</code></dt>
|
|
271
|
+
|
|
272
|
+
<dd>Perform HTTP authentication to log in a user. If that
|
|
273
|
+
doesn't work, fall back on a login form.</dd>
|
|
274
|
+
|
|
275
|
+
<dt><code>/user/logout</code></dt>
|
|
276
|
+
|
|
277
|
+
<dd>Log a user out. Actually loops back to the login action,
|
|
278
|
+
because the only way to get the browser to stop sending HTTP
|
|
279
|
+
authentication data with every request is to ask it to get new
|
|
280
|
+
authentication data from the user.</dd>
|
|
281
|
+
|
|
282
|
+
<dt><code>/user/new</code></dt>
|
|
283
|
+
|
|
284
|
+
<dd>Create a new user.</dd>
|
|
285
|
+
|
|
286
|
+
<dt><code>/user/show</code></dt>
|
|
287
|
+
|
|
288
|
+
<dd>Display information about a user. Administrators can see
|
|
289
|
+
more information than normal users. Normal users can see some
|
|
290
|
+
information on their own record that they will not see in the
|
|
291
|
+
records of other users.</dd>
|
|
292
|
+
</dl>
|
|
293
|
+
|
|
294
|
+
<p>Now that you have the software running, create a user using
|
|
295
|
+
<code>/user/new</code> The first user that you create will
|
|
296
|
+
automatically be granted the administrative privilege, sort of
|
|
297
|
+
like "super-user" status on a Unix or Linux system, and will be
|
|
298
|
+
logged in immediately.</p>
|
|
299
|
+
|
|
300
|
+
<p>You <i>should</i> be able to grant administrative status to
|
|
301
|
+
subsequent users by editing their record and setting the
|
|
302
|
+
<i>admin</i> field to 1, due to a Rails bug (at this writing,
|
|
303
|
+
October 2005), the scaffold views aren't capable of editing
|
|
304
|
+
boolean database fields. We won't need that facility to go
|
|
305
|
+
through our demo.</p>
|
|
306
|
+
|
|
307
|
+
<p>Log out of the application via the /user/logout action. Your
|
|
308
|
+
browser will present a login panel using HTTP authentication. Use
|
|
309
|
+
the <i>cancel</i> button or whatever mechanism your browser
|
|
310
|
+
provides to get rid of that panel. You should now see a login
|
|
311
|
+
form in an HTML page. Don't log in.</p>
|
|
312
|
+
|
|
313
|
+
<p>Run the <code>/user/list</code> action. This action requies a
|
|
314
|
+
login before it will complete. The browser will present the login
|
|
315
|
+
panel again. Log in to the application as your administrative
|
|
316
|
+
user. You'll now see the list of users.</p>
|
|
317
|
+
|
|
318
|
+
<h2>Learning About Login Management And The User Object</h2>
|
|
319
|
+
|
|
320
|
+
<p>Let's look at the code that made that login panel happen. In
|
|
321
|
+
app/controllers/user_controller.rb, you will find this code:</p>
|
|
322
|
+
|
|
323
|
+
<blockquote>
|
|
324
|
+
<pre>
|
|
325
|
+
# Require_login will require a login before the action can be completed.
|
|
326
|
+
# It uses Modal, so it will continue to the action if the login is
|
|
327
|
+
# successful.
|
|
328
|
+
before_filter :require_login, :only => [ :list, :show ]
|
|
329
|
+
|
|
330
|
+
</pre>
|
|
331
|
+
</blockquote>
|
|
332
|
+
|
|
333
|
+
<p>That code causes require_login to be called before the
|
|
334
|
+
<code>list</code> or <code>show</code> actions. Require_login
|
|
335
|
+
will keep trying to get a user logged in until the login succeeds
|
|
336
|
+
or the user bails out of the process using the back button. If
|
|
337
|
+
you don't yet understand how <i>before-filters</i> work, you'll
|
|
338
|
+
need to peruse the Rails documentation. If a user is already
|
|
339
|
+
logged in, require_login returns <code>true</code> and execution
|
|
340
|
+
proceeds. If no user is logged in, <code>require_login</code>
|
|
341
|
+
will save the current action and then redirect the browser to the
|
|
342
|
+
<code>/usr/login</code> action. Once the user is logged in
|
|
343
|
+
successfully, the browser will be redirected to the previous
|
|
344
|
+
action.</p>
|
|
345
|
+
|
|
346
|
+
<p>Although you might believe that all of this is taking place
|
|
347
|
+
sequentially in your Rails application, that's not the case. It's
|
|
348
|
+
achieved by sending a redirect to the browser as each step
|
|
349
|
+
completes. At each step the browser requests the URL it's been
|
|
350
|
+
redirected to, and Rails carries out an individual action.
|
|
351
|
+
Execution proceeds this way if the user asks for
|
|
352
|
+
<code>/user/list</code> while not logged in:</p>
|
|
353
|
+
|
|
354
|
+
<ol>
|
|
355
|
+
<li>The user asks for <code>/user/list</code></li>
|
|
356
|
+
|
|
357
|
+
<li>Rails redirects the browser to
|
|
358
|
+
<code>/user/login</code></li>
|
|
359
|
+
|
|
360
|
+
<li>The browser asks for <code>/user/login</code></li>
|
|
361
|
+
|
|
362
|
+
<li>Rails sets the HTTP header to request HTTP authentication
|
|
363
|
+
and sends a login form.</li>
|
|
364
|
+
|
|
365
|
+
<li>The browser sends login information.</li>
|
|
366
|
+
|
|
367
|
+
<li>Rails redirects the browser to <code>/user/list</code></li>
|
|
368
|
+
|
|
369
|
+
<li>The browser requests <code>/user/list</code></li>
|
|
370
|
+
|
|
371
|
+
<li>Rails finally sends the list of users.</li>
|
|
372
|
+
</ol>Fortunately, all of this complication is handled for you.
|
|
373
|
+
All you need to know is that you should set
|
|
374
|
+
<code>require_login</code> as a before-filter for any action that
|
|
375
|
+
should only be executed for logged-in users.
|
|
376
|
+
|
|
377
|
+
<p>There's also a <code>require_admin</code> filter. This one is
|
|
378
|
+
used with the <code>destroy</code> action of the user controller,
|
|
379
|
+
as shown by this code:</p>
|
|
380
|
+
|
|
381
|
+
<blockquote>
|
|
382
|
+
<pre>
|
|
383
|
+
# Require_admin will require an administrative login before the action
|
|
384
|
+
# can be called. It uses Modal, so it will continue to the action if the
|
|
385
|
+
# login is successful.
|
|
386
|
+
before_filter :require_admin, :only => [ :destroy ]
|
|
387
|
+
|
|
388
|
+
</pre>
|
|
389
|
+
</blockquote>
|
|
390
|
+
|
|
391
|
+
<p>That will prevent the <code>destroy</code> action from being
|
|
392
|
+
executed unless the user successfully logs in as an
|
|
393
|
+
administrator.</p>
|
|
394
|
+
|
|
395
|
+
<p><code>require_admin</code> and <code>require_login</code> are
|
|
396
|
+
part of your controller-based security setup. As the first line
|
|
397
|
+
of defense, they will be where potential security violations
|
|
398
|
+
<i>should</i> stop.</p>
|
|
399
|
+
|
|
400
|
+
<p>There's only a little more to learn about logging in and
|
|
401
|
+
users. Take a look at the file
|
|
402
|
+
<code>app/controllers/application.rb</code> . You'll see these
|
|
403
|
+
lines:</p>
|
|
404
|
+
|
|
405
|
+
<blockquote>
|
|
406
|
+
<code>require 'user_support'<br />
|
|
407
|
+
<br />
|
|
408
|
+
class ApplicationController <
|
|
409
|
+
ActionController::Base<br /></code>
|
|
410
|
+
|
|
411
|
+
<blockquote>
|
|
412
|
+
<code>helper :ModelSecurity</code><br />
|
|
413
|
+
<b><code>include UserSupport</code></b><br />
|
|
414
|
+
<b><code>before_filter :user_setup</code></b>
|
|
415
|
+
</blockquote><code>end</code>
|
|
416
|
+
</blockquote>
|
|
417
|
+
|
|
418
|
+
<p>Requiring the file <code>user_support</code> and including the
|
|
419
|
+
module <code>UserSupport</code> mix the login and user-management
|
|
420
|
+
capabilities into the application controller's class. The
|
|
421
|
+
before-filter <code>user_setup</code> handles session control and
|
|
422
|
+
login management for the entire application. Once
|
|
423
|
+
<code>user_setup</code> has run, the <code>User</code> object for
|
|
424
|
+
the currently-logged-in user can be accessed through
|
|
425
|
+
<code>User.current</code> .</p>
|
|
426
|
+
|
|
427
|
+
<h2>Introducing ModelSecurity</h2>
|
|
428
|
+
|
|
429
|
+
<p>That's all you need to know to use the <code>User</code>
|
|
430
|
+
system to manage logins. But we'll keep looking at the
|
|
431
|
+
<code>User</code> code to see how it secures its own data using
|
|
432
|
+
<code>ModelSecurity</code>.</p>
|
|
433
|
+
|
|
434
|
+
<p>Create a second user with <code>/user/new</code> . This user
|
|
435
|
+
won't be privileged. The system will send you an email with
|
|
436
|
+
instructions on how to confirm that user's login. Just in case
|
|
437
|
+
email delivery isn't working on your server, you can get the
|
|
438
|
+
email text out of the log file <code>logs/development.log</code>
|
|
439
|
+
. Confirm the user's login.</p>
|
|
440
|
+
|
|
441
|
+
<p>Logged in as your administrative user, visit
|
|
442
|
+
<code>/user/list</code> . That will display a list of the users
|
|
443
|
+
you've created so far. Because you are the administrator, their
|
|
444
|
+
emails will be visible to you. Now, log out using
|
|
445
|
+
<code>/user/logout</code> and log back in as the non-privileged
|
|
446
|
+
user you created. Visit <code>/user/list</code> again. Now,
|
|
447
|
+
you'll be able to see your own email, but not that of other
|
|
448
|
+
users.</p>
|
|
449
|
+
|
|
450
|
+
<p>Let's look at the code that makes that happen. Edit
|
|
451
|
+
<code>app/models/user.rb</code> and find these lines:</p>
|
|
452
|
+
|
|
453
|
+
<blockquote>
|
|
454
|
+
<pre>
|
|
455
|
+
# If this is a new (never saved) record, or if this record corresponds to
|
|
456
|
+
# the currently-logged-in user, allow reading of the email address.
|
|
457
|
+
let_read :email, :if => :new_or_me_or_logging_in?
|
|
458
|
+
|
|
459
|
+
</pre>
|
|
460
|
+
</blockquote>
|
|
461
|
+
|
|
462
|
+
<p>This code, in the declaration of the User class, says that the
|
|
463
|
+
<code>email</code> attribute of the model can be read if the
|
|
464
|
+
function <code>new_or_me_or_logging_in?</code> returns true.
|
|
465
|
+
Let's look at that function:</p>
|
|
466
|
+
|
|
467
|
+
<blockquote>
|
|
468
|
+
<pre>
|
|
469
|
+
# Return true if the user record is new (never been saved) or if it
|
|
470
|
+
# corresponds to the currently-logged-in user, or if the current user
|
|
471
|
+
# is the special "login" user. This security test is a common pattern
|
|
472
|
+
# applied to a number of user record attributes.
|
|
473
|
+
def new_or_me_or_logging_in?
|
|
474
|
+
new_record? or User.current == self or logging_in?
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
</pre>
|
|
478
|
+
</blockquote>
|
|
479
|
+
|
|
480
|
+
<p>The first condition is the Rails function
|
|
481
|
+
<code>new_record?</code> which would return true if the record
|
|
482
|
+
has never been saved. The second condition would return true if
|
|
483
|
+
the record refers to the currently-logged-in user, and this is
|
|
484
|
+
what allows the user to see his own email address. The third
|
|
485
|
+
condition returns true if there is currently no logged-in user
|
|
486
|
+
(it contains the expression <code>User.current == nil</code>).
|
|
487
|
+
These are all of the cases in which the application should allow
|
|
488
|
+
reading of the <code>email</code> attribute of a
|
|
489
|
+
<code>User</code> record, <i>except one.</i></p>
|
|
490
|
+
|
|
491
|
+
<p>NOTE: at this time, there is a app/views/user/list.rhtml
|
|
492
|
+
template installed by the generator that overrides the scaffold.
|
|
493
|
+
The difference between this file and the scaffold template is
|
|
494
|
+
that ActiveRecord#readable? is used to determine if a datum
|
|
495
|
+
should be displayed or not. This file will move to
|
|
496
|
+
app/views/scaffolds/list.rhtml once I figure out how to override
|
|
497
|
+
the scaffold template location. - Bruce</p>
|
|
498
|
+
|
|
499
|
+
<p>What tells the software that the administrator can read
|
|
500
|
+
everyone's email address? That's another line in the
|
|
501
|
+
<code>User</code> model declaration:</p>
|
|
502
|
+
|
|
503
|
+
<blockquote>
|
|
504
|
+
<pre>
|
|
505
|
+
# Let the administrator access all data. This implements a Unix-like
|
|
506
|
+
# super-user. Note that the coarse-grained override of the super-user
|
|
507
|
+
# is not a _necessary_ pattern for the ModelSecurity module, you can
|
|
508
|
+
# implement controls as fine-grained as you like.
|
|
509
|
+
let_access :all, :if => :admin?
|
|
510
|
+
|
|
511
|
+
</pre>
|
|
512
|
+
</blockquote>
|
|
513
|
+
|
|
514
|
+
<p><code>let_access</code> calls <code>let_read</code> and
|
|
515
|
+
<code>let_write</code> with the same arguments, and thus sets
|
|
516
|
+
both read and write permissions. <code>:all</code> is a special
|
|
517
|
+
name that means the permission applies to all of the attributes
|
|
518
|
+
of the data model, not just one database field. And
|
|
519
|
+
<code>admin?</code> is a function that returns true if the
|
|
520
|
+
currently-logged-in user has the administrative privilege. It
|
|
521
|
+
just tests the value of a model attribute called
|
|
522
|
+
<code>admin</code>. The effect of all of this is that the
|
|
523
|
+
administrative user can read or write any datum.</p>
|
|
524
|
+
|
|
525
|
+
<p>You must start by setting the permissions for
|
|
526
|
+
<code>:all</code> in your model if you are using ModelSecurity,
|
|
527
|
+
as the default is to permit all accesses. If you won't be setting
|
|
528
|
+
up an administrative user, as above, it would be a good idea to
|
|
529
|
+
make the default "no access". You can do that with the following
|
|
530
|
+
code:</p>
|
|
531
|
+
|
|
532
|
+
<blockquote>
|
|
533
|
+
<pre>
|
|
534
|
+
# Set the default to "no access".
|
|
535
|
+
let_access :all, :if => :never?
|
|
536
|
+
|
|
537
|
+
</pre>
|
|
538
|
+
</blockquote>
|
|
539
|
+
|
|
540
|
+
<p>The <code>never?</code> test is provided with ModelSecurity,
|
|
541
|
+
and always returns <code>false</code>. A companion test
|
|
542
|
+
<code>always?</code> reliably returns <code>true</code>.</p>
|
|
543
|
+
|
|
544
|
+
<p>ModelSecurity has two mechanisms for protecting data. One
|
|
545
|
+
protects the actual data model and will throw a exception upon an
|
|
546
|
+
unpermitted access. But that's not what we've been seeing: our
|
|
547
|
+
views have quietly refused to display some unpermitted data,
|
|
548
|
+
without any exceptions. A second mechanism prevents the common
|
|
549
|
+
view functions, including those used by templates, from
|
|
550
|
+
displaying or editing data inappropriately. You activate that
|
|
551
|
+
mechanism by declaring <code>ModelSecurityHelper</code> in your
|
|
552
|
+
application, as below in
|
|
553
|
+
<code>app/controllers/application.rb</code>:</p>
|
|
554
|
+
|
|
555
|
+
<blockquote>
|
|
556
|
+
<code>require 'user_support'<br />
|
|
557
|
+
<br />
|
|
558
|
+
class ApplicationController <
|
|
559
|
+
ActionController::Base<br /></code>
|
|
560
|
+
|
|
561
|
+
<blockquote>
|
|
562
|
+
<b><code>helper :ModelSecurity</code></b><br />
|
|
563
|
+
<code>include UserSupport</code><br />
|
|
564
|
+
<br />
|
|
565
|
+
<code>before_filter :user_setup</code>
|
|
566
|
+
</blockquote><code>end</code>
|
|
567
|
+
</blockquote>
|
|
568
|
+
|
|
569
|
+
<p>This makes the code in <code>ModelSecurityHelper</code>
|
|
570
|
+
available to all views in the application. ModelSecurityHelper
|
|
571
|
+
redefines these common view functions to respect the data model
|
|
572
|
+
permissions:</p>
|
|
573
|
+
|
|
574
|
+
<ul style="list-style-type: none">
|
|
575
|
+
<li>check_box</li>
|
|
576
|
+
|
|
577
|
+
<li>file_field</li>
|
|
578
|
+
|
|
579
|
+
<li>hidden_field</li>
|
|
580
|
+
|
|
581
|
+
<li>password_field</li>
|
|
582
|
+
|
|
583
|
+
<li>radio_button</li>
|
|
584
|
+
|
|
585
|
+
<li>text_area</li>
|
|
586
|
+
|
|
587
|
+
<li>text_field</li>
|
|
588
|
+
</ul>Views that use those functions exclusively to display and
|
|
589
|
+
edit database fields will always refrain from making
|
|
590
|
+
inappropriate read or write accesses.
|
|
591
|
+
|
|
592
|
+
<p>One complication of the view protection is that instance
|
|
593
|
+
variables of your model that are not related to database fields
|
|
594
|
+
are effected in the same way as model attributes. So, when you
|
|
595
|
+
declare the accessors for those instance variables with code like
|
|
596
|
+
this:</p>
|
|
597
|
+
|
|
598
|
+
<blockquote>
|
|
599
|
+
<pre>
|
|
600
|
+
attr_accessible :password, :password_confirmation, :old_password
|
|
601
|
+
</pre>
|
|
602
|
+
</blockquote>
|
|
603
|
+
|
|
604
|
+
<p>You should also declare security permissions on those same
|
|
605
|
+
variables:</p>
|
|
606
|
+
|
|
607
|
+
<blockquote>
|
|
608
|
+
<pre>
|
|
609
|
+
# NOTE: :password, :password_confirmation, and :old_password
|
|
610
|
+
# are not attributes of the record, they are instance variables of the
|
|
611
|
+
# class and aren't written to disk under those names. But I declare them
|
|
612
|
+
# here because otherwise ModelSecurityHelper (which doesn't know that)
|
|
613
|
+
# isn't going to allow me to enter them into a form field.
|
|
614
|
+
#
|
|
615
|
+
# I like how fine-grained I can get.
|
|
616
|
+
let_write :password, :if => :new_or_me_or_logging_in?
|
|
617
|
+
let_write :password_confirmation, :if => :new_or_me?
|
|
618
|
+
let_write :old_password, :if => :me?
|
|
619
|
+
</pre>
|
|
620
|
+
</blockquote>
|
|
621
|
+
|
|
622
|
+
<h2>Display Control</h2>
|
|
623
|
+
|
|
624
|
+
<p>You may also have noticed that there are more fields in the
|
|
625
|
+
database, and more attributes in the User object, than the view
|
|
626
|
+
displays when you visit <code>/user/list</code> . But a quick
|
|
627
|
+
perusal of the code will show that it's a scaffold view. You
|
|
628
|
+
would have expected a scaffold to display all fields. But
|
|
629
|
+
ModelSecurity allows us to override that with the
|
|
630
|
+
<code>let_display</code> declaration. Take a look at its use in
|
|
631
|
+
the <code>User</code> model declaration:</p>
|
|
632
|
+
|
|
633
|
+
<blockquote>
|
|
634
|
+
<pre>
|
|
635
|
+
# These just control display.
|
|
636
|
+
let_display :all, :if => :never? # Set default to don't display, override below.
|
|
637
|
+
let_display :admin, :activated, :if => :admin?
|
|
638
|
+
let_display :login, :name, :email
|
|
639
|
+
</pre>
|
|
640
|
+
</blockquote>
|
|
641
|
+
|
|
642
|
+
<p>This declaration will cause the scaffold view to present
|
|
643
|
+
information about three fields, <code>login</code>,
|
|
644
|
+
<code>name</code> and <code>email</code> to all users (although a
|
|
645
|
+
<code>let_read</code> declaration further restricts whose email
|
|
646
|
+
address a non-privileged user can see, as above). Administrative
|
|
647
|
+
users will also see the fields <code>admin</code> and
|
|
648
|
+
<code>activated</code>. Nobody will see the fields
|
|
649
|
+
<code>cypher</code> and <code>salt</code>, they are long strings
|
|
650
|
+
of encrypted or random cybercrud that nobody wants to read. Note
|
|
651
|
+
that the scaffold view will format the display as a table with
|
|
652
|
+
columns only for the data that it will display. All of this is
|
|
653
|
+
achieved by overloading the <code>content_columns</code> method
|
|
654
|
+
of <code>ActiveRecord</code> to filter the columns it reports
|
|
655
|
+
using the data in the <code>let_display</code> declaration.</p>
|
|
656
|
+
|
|
657
|
+
<p>While the tests used with <code>let_read</code>,
|
|
658
|
+
<code>let_write</code>, and <code>let_access</code> are instance
|
|
659
|
+
methods of your <code>ActiveRecord</code> class, tests used with
|
|
660
|
+
<code>let_display</code> must be class methods. This is because
|
|
661
|
+
the decision of what columns to display is made before the first
|
|
662
|
+
row's instance is accessed. Rails makes it easy enough to declare
|
|
663
|
+
a method to work with both an instance and its class.</p>
|
|
664
|
+
|
|
665
|
+
<p>You've now completed the ModelSecurity tutorial. For more
|
|
666
|
+
information, see the <a href=
|
|
667
|
+
"http://perens.com/FreeSoftware/ModelSecurity/Reference/">reference</a>.</p>
|
|
668
|
+
</body>
|
|
669
|
+
</html>
|
|
@@ -20,7 +20,7 @@ private
|
|
|
20
20
|
# Require_admin will require an administrative login before the action
|
|
21
21
|
# can be called. It uses Modal, so it will continue to the action if the
|
|
22
22
|
# login is successful.
|
|
23
|
-
before_filter :require_admin, :only => [ :destroy ]
|
|
23
|
+
before_filter :require_admin, :only => [ :configure, :destroy ]
|
|
24
24
|
|
|
25
25
|
# Require_login will require a login before the action
|
|
26
26
|
# can be called. It uses Modal, so it will continue to the action if the
|
|
@@ -36,6 +36,26 @@ private
|
|
|
36
36
|
render :status => 401
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Promote the first user to be an administrator, and don't make that user
|
|
40
|
+
# confirm.
|
|
41
|
+
def promote
|
|
42
|
+
@user.activated = 1
|
|
43
|
+
@user.admin = 1
|
|
44
|
+
@user.save
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Send an email to a user with details on how to confirm his login.
|
|
48
|
+
def send_confirmation_email(params)
|
|
49
|
+
url_params = {
|
|
50
|
+
:controller => 'user',
|
|
51
|
+
:action => 'activate',
|
|
52
|
+
:id => @user.id,
|
|
53
|
+
:token => @user.new_token
|
|
54
|
+
}
|
|
55
|
+
url = url_for(url_params)
|
|
56
|
+
UserMailer.deliver_new_user(params, url, @user.token_expiry)
|
|
57
|
+
end
|
|
58
|
+
|
|
39
59
|
public
|
|
40
60
|
|
|
41
61
|
# Activate a new user, having logged in with a security token. All of the
|
|
@@ -43,6 +63,102 @@ public
|
|
|
43
63
|
def activate
|
|
44
64
|
end
|
|
45
65
|
|
|
66
|
+
before_filter :require_admin, :only => [ :configure, :destroy ]
|
|
67
|
+
|
|
68
|
+
# Require_login will require a login before the action
|
|
69
|
+
# can be called. It uses Modal, so it will continue to the action if the
|
|
70
|
+
# login is successful.
|
|
71
|
+
before_filter :require_login, :only => [ :list, :show ]
|
|
72
|
+
|
|
73
|
+
# Cause HTTP Basic validation.
|
|
74
|
+
# FIX: Basic is not secure. Change to Digest authentication.
|
|
75
|
+
def http_authorize(realm=@request.domain(2))
|
|
76
|
+
# This will cause the browser to send HTTP authorization information.
|
|
77
|
+
@response.headers["Status"] = "Unauthorized"
|
|
78
|
+
@response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
|
|
79
|
+
render :status => 401
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Activate a new user, having logged in with a security token. All of the
|
|
83
|
+
# work goes on in user_support.
|
|
84
|
+
def activate
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Send out a forgot-password email.
|
|
88
|
+
def forgot_password
|
|
89
|
+
case @request.method
|
|
90
|
+
when :get
|
|
91
|
+
@user = User.new
|
|
92
|
+
when :post
|
|
93
|
+
@user = User.find_first(['email = ?', @params['user']['email']])
|
|
94
|
+
if @user
|
|
95
|
+
url = url_for(:controller => 'user', :action => 'activate', :id => @user.id, :token => @user.new_token)
|
|
96
|
+
UserMailer.deliver_forgot_password(@user, url)
|
|
97
|
+
render :action => 'forgot_password_done'
|
|
98
|
+
else
|
|
99
|
+
flash['notice'] = "Can't find a user with email #{@params['email']}."
|
|
100
|
+
@user = User.new
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Tell the user the email's on the way.
|
|
106
|
+
def forgot_password_done
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def list
|
|
110
|
+
@users = User.find_all
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Attempt HTTP authentication, and fall back on a login form.
|
|
114
|
+
# If this method is called login_admin (it's an alias), keep trying
|
|
115
|
+
# until an administrator logs in or the user pushes the "back" button.
|
|
116
|
+
def login
|
|
117
|
+
if flash[:login_succeeded]
|
|
118
|
+
redirect_back_or_default :action => :success
|
|
119
|
+
return
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@user = User.new
|
|
123
|
+
|
|
124
|
+
flash[:login_succeeded] = false
|
|
125
|
+
http_authorize
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Log in an administrator. If a non-administrator logs in, keep trying
|
|
129
|
+
# until an administrator logs in or the user pushes the "back" button.
|
|
130
|
+
alias login_admin login
|
|
131
|
+
|
|
132
|
+
# Log out the current user, attempt HTTP authentication to log in a new
|
|
133
|
+
# user. The session information skip_user_setup=true tells the server to
|
|
134
|
+
# generate a new HTTP authentication request and ignore the current HTTP
|
|
135
|
+
# authentication data.
|
|
136
|
+
# We have to request new HTTP authentication here to make the browser
|
|
137
|
+
# forget the old authentication data. Otherwise, the browser keeps sending
|
|
138
|
+
# it!
|
|
139
|
+
def logout
|
|
140
|
+
User.sign_off
|
|
141
|
+
reset_session
|
|
142
|
+
flash[:skip_user_setup] = true
|
|
143
|
+
redirect_to :action => 'login'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Configure this system.
|
|
147
|
+
def configure
|
|
148
|
+
case @request.method
|
|
149
|
+
when :get
|
|
150
|
+
@user_configuration = UserConfiguration.get
|
|
151
|
+
when :post
|
|
152
|
+
c = (@user_configuration = UserConfiguration.get)
|
|
153
|
+
c.attributes = @params['user_configuration']
|
|
154
|
+
if c.save
|
|
155
|
+
flash['notice'] = "Configuration saved."
|
|
156
|
+
else
|
|
157
|
+
flash['notice'] = "Configuration NOT saved."
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
46
162
|
# Send out a forgot-password email.
|
|
47
163
|
def forgot_password
|
|
48
164
|
case @request.method
|
|
@@ -106,27 +222,27 @@ public
|
|
|
106
222
|
when :post
|
|
107
223
|
p = @params['user']
|
|
108
224
|
@user = User.new(p)
|
|
225
|
+
|
|
226
|
+
if UserConfiguration.get.email_confirmation == 0
|
|
227
|
+
@user.activated = 1
|
|
228
|
+
end
|
|
229
|
+
|
|
109
230
|
if @user.save
|
|
110
231
|
flash['notice'] = 'User created.'
|
|
111
232
|
# Promote the very first user to be an administrator.
|
|
112
233
|
if @user.id == 1
|
|
113
|
-
|
|
114
|
-
@user.activated = 1
|
|
115
|
-
@user.save
|
|
234
|
+
promote
|
|
116
235
|
User.current = @user
|
|
117
236
|
render :action => 'admin_created'
|
|
118
|
-
# Mail the user instructions on how to activate their account.
|
|
119
237
|
else
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@email = p['email']
|
|
129
|
-
render :action => 'created'
|
|
238
|
+
# Mail the user instructions on how to activate their account.
|
|
239
|
+
if UserConfiguration.get.email_confirmation == 1
|
|
240
|
+
send_confirmation_email(p)
|
|
241
|
+
@email = p['email']
|
|
242
|
+
render :action => 'created'
|
|
243
|
+
else
|
|
244
|
+
render :action => 'success'
|
|
245
|
+
end
|
|
130
246
|
end
|
|
131
247
|
else
|
|
132
248
|
flash['notice'] = 'Creation of new user failed.'
|
data/templates/db/demo.sql
CHANGED
|
@@ -5,7 +5,7 @@ end
|
|
|
5
5
|
|
|
6
6
|
module ActiveRecord
|
|
7
7
|
class Base
|
|
8
|
-
once('@
|
|
8
|
+
once('@modelSecurityInstanceAliasesDone') {
|
|
9
9
|
# Provides access to the pre-overload version of read_attribute, which
|
|
10
10
|
# is called by the overloaded version.
|
|
11
11
|
alias :old_read_attribute :read_attribute
|
|
@@ -15,9 +15,17 @@ module ActiveRecord
|
|
|
15
15
|
alias :old_write_attribute :write_attribute
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
def readable?(name)
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def writable?(name)
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
18
26
|
class << self
|
|
19
27
|
private
|
|
20
|
-
once('@
|
|
28
|
+
once('@modelSecurityClassAliasesDone') {
|
|
21
29
|
# Provides access to the pre-overload version of content_columns, which
|
|
22
30
|
# is called by the overloaded version.
|
|
23
31
|
alias :old_content_columns :content_columns
|
|
@@ -32,6 +40,11 @@ module ActiveRecord
|
|
|
32
40
|
not display?(c.name)
|
|
33
41
|
}
|
|
34
42
|
end
|
|
43
|
+
|
|
44
|
+
# Always return true. ModelSecurity.display? will override this.
|
|
45
|
+
def display?(name)
|
|
46
|
+
true
|
|
47
|
+
end
|
|
35
48
|
end
|
|
36
49
|
end
|
|
37
50
|
end
|
|
@@ -96,7 +109,7 @@ module ActionView
|
|
|
96
109
|
|
|
97
110
|
# Evaluating the aliases twice would break them.
|
|
98
111
|
# Evaluating the constant definition twice causes a complaint.
|
|
99
|
-
once ('@
|
|
112
|
+
once ('@modelSecurityAliasesDone') {
|
|
100
113
|
alias :old_check_box :check_box
|
|
101
114
|
alias :old_radio_button :radio_button
|
|
102
115
|
}
|
data/templates/lib/modal.rb
CHANGED
|
@@ -32,12 +32,15 @@ module Modal
|
|
|
32
32
|
if r = @params['ret']
|
|
33
33
|
logger.info("modal_setup: Save location #{r.sub(/\.L/, '#L')}")
|
|
34
34
|
self.return_location = r.sub(/\.L/, '#L')
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
# This results in redirection loops, as currently written.
|
|
36
|
+
# It needs a hueristic to carefully avoid them before we try it again.
|
|
37
|
+
#
|
|
38
|
+
# elsif return_location.nil?
|
|
39
|
+
# r = @request.env['HTTP_REFERER']
|
|
40
|
+
# if r
|
|
41
|
+
# logger.info("modal_setup: Save HTTP Referer #{r}")
|
|
42
|
+
# self.return_location = @request.env['HTTP_REFERER']
|
|
43
|
+
# end
|
|
41
44
|
end
|
|
42
45
|
true
|
|
43
46
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class UserConfiguration < ActiveRecord::Base
|
|
2
|
+
public
|
|
3
|
+
attr_accessible :email_confirmation, :email_sender
|
|
4
|
+
|
|
5
|
+
def self.get
|
|
6
|
+
begin
|
|
7
|
+
u = self.find(1)
|
|
8
|
+
rescue
|
|
9
|
+
c = self.new
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(parameters = nil)
|
|
14
|
+
super
|
|
15
|
+
self.email_confirmation = 1
|
|
16
|
+
self.email_sender = ''
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -2,7 +2,15 @@ ActionMailer::Base.delivery_method = :sendmail
|
|
|
2
2
|
|
|
3
3
|
# Send mail to a user to administer that user's login. Called by UserController.
|
|
4
4
|
class UserMailer < ActionMailer::Base
|
|
5
|
+
private
|
|
6
|
+
def set_sender
|
|
7
|
+
s = UserConfiguration.get.email_sender
|
|
8
|
+
if s.length > 0
|
|
9
|
+
from s
|
|
10
|
+
end
|
|
11
|
+
end
|
|
5
12
|
|
|
13
|
+
public
|
|
6
14
|
# Send a forgot-password email, allowing the user to regain their login name
|
|
7
15
|
# and password.
|
|
8
16
|
def forgot_password(user, url)
|
|
@@ -11,8 +19,7 @@ class UserMailer < ActionMailer::Base
|
|
|
11
19
|
|
|
12
20
|
recipients user.email
|
|
13
21
|
subject 'Login and password recovery.'
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
set_sender
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
# Send a new-user email, providing the user with a URL used to validate
|
|
@@ -24,7 +31,6 @@ class UserMailer < ActionMailer::Base
|
|
|
24
31
|
|
|
25
32
|
recipients params['email']
|
|
26
33
|
subject 'Your new login is ready'
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
set_sender
|
|
29
35
|
end
|
|
30
36
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<h1>Configure the User System</h1>
|
|
2
|
+
|
|
3
|
+
<% if flash['notice'] %>
|
|
4
|
+
<p><%= flash['notice'] %></p>
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
<%= error_messages_for 'user_configuration' %>
|
|
8
|
+
<%= form_tag :action => 'configure' %>
|
|
9
|
+
<table>
|
|
10
|
+
<tr>
|
|
11
|
+
<th><label for="user_configuration_email_confirmation">
|
|
12
|
+
Email confirmation:
|
|
13
|
+
</label></th>
|
|
14
|
+
<td>
|
|
15
|
+
<%= check_box 'user_configuration', 'email_confirmation' %>
|
|
16
|
+
</td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<th><label for="user_configuration_email_sender">
|
|
20
|
+
Email Sender:
|
|
21
|
+
</label></th>
|
|
22
|
+
<td><%= text_field 'user_configuration', 'email_sender' %></td>
|
|
23
|
+
</tr>
|
|
24
|
+
</table>
|
|
25
|
+
<%= submit_tag "Save" %>
|
|
26
|
+
<%= end_form_tag %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<h1>Listing <%= @scaffold_plural_name %></h1>
|
|
2
|
+
|
|
3
|
+
<table>
|
|
4
|
+
<tr>
|
|
5
|
+
<% for column in User.content_columns %>
|
|
6
|
+
<th><%= column.human_name %></th>
|
|
7
|
+
<% end %>
|
|
8
|
+
</tr>
|
|
9
|
+
|
|
10
|
+
<% for entry in @users %>
|
|
11
|
+
<tr>
|
|
12
|
+
<% for column in User.content_columns %>
|
|
13
|
+
<td>
|
|
14
|
+
<% if entry.readable?(column.name) %>
|
|
15
|
+
<%= entry.send(column.name) %>
|
|
16
|
+
<% end %>
|
|
17
|
+
</td>
|
|
18
|
+
<% end %>
|
|
19
|
+
<td><%= link_to "Show", :action => "show", :id => entry %></td>
|
|
20
|
+
<td><%= link_to "Edit", :action => "edit", :id => entry %></td>
|
|
21
|
+
<td><%= link_to "Destroy", {:action => "destroy", :id => entry}, {:confirm => "Are you sure?"} %></td>
|
|
22
|
+
</tr>
|
|
23
|
+
<% end %>
|
|
24
|
+
</table>
|
|
25
|
+
|
|
26
|
+
<br />
|
|
27
|
+
|
|
28
|
+
<%= link_to "New User", :action => "new" %>
|
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.
|
|
7
|
-
date: 2005-10-
|
|
6
|
+
version: 0.0.8
|
|
7
|
+
date: 2005-10-12
|
|
8
8
|
summary: "[Rails] Model security and authentication generator."
|
|
9
9
|
require_paths:
|
|
10
10
|
- "."
|
|
@@ -32,10 +32,13 @@ files:
|
|
|
32
32
|
- templates/README
|
|
33
33
|
- templates/USAGE
|
|
34
34
|
- templates/RUN_DEMO
|
|
35
|
+
- templates/Tutorial.html
|
|
35
36
|
- templates/ADD_TO_APPLICATION_CONTROLLER
|
|
36
37
|
- templates/controllers/user_controller.rb
|
|
37
38
|
- templates/db/demo.sql
|
|
39
|
+
- templates/db/schema.sql
|
|
38
40
|
- templates/db/users.sql
|
|
41
|
+
- templates/db/user_configurations.sql
|
|
39
42
|
- templates/helpers/modal_helper.rb
|
|
40
43
|
- templates/helpers/model_security_helper.rb
|
|
41
44
|
- templates/lib/modal.rb
|
|
@@ -45,6 +48,7 @@ files:
|
|
|
45
48
|
- templates/mailer/forgot_password.rhtml
|
|
46
49
|
- templates/mailer/new_user.rhtml
|
|
47
50
|
- templates/models/user.rb
|
|
51
|
+
- templates/models/user_configuration.rb
|
|
48
52
|
- templates/models/user_mailer.rb
|
|
49
53
|
- templates/test/mock_mailer.rb
|
|
50
54
|
- templates/test/mock_time.rb
|
|
@@ -53,11 +57,12 @@ files:
|
|
|
53
57
|
- templates/test/users.yml
|
|
54
58
|
- templates/views/activate.rhtml
|
|
55
59
|
- templates/views/admin_created.rhtml
|
|
60
|
+
- templates/views/configure.rhtml
|
|
56
61
|
- templates/views/created.rhtml
|
|
57
62
|
- templates/views/forgot_password.rhtml
|
|
58
63
|
- templates/views/forgot_password_done.rhtml
|
|
64
|
+
- templates/views/list.rhtml
|
|
59
65
|
- templates/views/login.rhtml
|
|
60
|
-
- templates/views/login_admin.rhtml
|
|
61
66
|
- templates/views/logout.rhtml
|
|
62
67
|
- templates/views/new.rhtml
|
|
63
68
|
- templates/views/success.rhtml
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<p>
|
|
2
|
-
You must have the administrator role to proceed.<br/>
|
|
3
|
-
If you aren't an administrator, please use the "back" button in your browser to exit this action.
|
|
4
|
-
</p>
|
|
5
|
-
<p>
|
|
6
|
-
<b>Administrator login</b>
|
|
7
|
-
</p>
|
|
8
|
-
<%= start_form_tag %>
|
|
9
|
-
<p><label for="user_login">User ID</label><br/>
|
|
10
|
-
<%= text_field 'user', 'login' %></p>
|
|
11
|
-
|
|
12
|
-
<p><label for="user_password">Password</label><br/>
|
|
13
|
-
<%= password_field 'user', 'password' %></p>
|
|
14
|
-
|
|
15
|
-
<%= submit_tag 'Login' %>
|
|
16
|
-
<%= end_form_tag %>
|