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