Oops, missing a couple C::M::DBIC::Schema version references
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / Authorization.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Part 6: Authorization
4
5
6 =head1 OVERVIEW
7
8 This is B<Part 6 of 10> for the Catalyst tutorial.
9
10 L<Tutorial Overview|Catalyst::Manual::Tutorial>
11
12 =over 4
13
14 =item 1
15
16 L<Introduction|Catalyst::Manual::Tutorial::Intro>
17
18 =item 2
19
20 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
21
22 =item 3
23
24 L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
25
26 =item 4
27
28 L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
29
30 =item 5
31
32 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
33
34 =item 6
35
36 B<Authorization>
37
38 =item 7
39
40 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
41
42 =item 8
43
44 L<Testing|Catalyst::Manual::Tutorial::Testing>
45
46 =item 9
47
48 L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
49
50 =item 10
51
52 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
53
54 =back
55
56
57 =head1 DESCRIPTION
58
59 This part of the tutorial adds role-based authorization to the existing
60 authentication implemented in Part 5.  It provides simple examples of
61 how to use roles in both TT templates and controller actions.  The first
62 half looks at manually configured authorization.  The second half looks
63 at how the ACL authorization plugin can simplify your code.
64
65 You can checkout the source code for this example from the catalyst
66 subversion repository as per the instructions in
67 L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
68
69
70 =head1 BASIC AUTHORIZATION
71
72 In this section you learn how to manually configure authorization.
73
74
75 =head2 Update Plugins to Include Support for Authorization
76
77 Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
78
79     __PACKAGE__->setup(qw/
80             -Debug
81             ConfigLoader
82             Static::Simple
83     
84             StackTrace
85     
86             Authentication
87             Authorization::Roles
88     
89             Session
90             Session::Store::FastMmap
91             Session::State::Cookie
92         /);
93
94 B<Note:> As discussed in MoreCatalystBasics, different versions of 
95 C<Catalyst::Devel> have used a variety of methods to load the plugins. 
96 You can put the plugins in the C<use Catalyst> statement if you prefer.
97
98
99 =head2 Add Config Information for Authorization
100
101 Edit C<myapp.conf> and update it to match the following (the
102 C<role_relation> and C<role_field> definitions are new):
103
104     # rename this file to MyApp.yml and put a : in front of "name" if
105     # you want to use yaml like in old versions of Catalyst
106     name MyApp
107     <authentication>
108         default_realm dbic
109         <realms>
110             <dbic>
111                 <credential>
112                     # Note this first definition would be the same as setting
113                     # __PACKAGE__->config->{authentication}->{realms}->{dbic}
114                     #     ->{credential} = 'Password' in lib/MyApp.pm
115                     #
116                     # Specify that we are going to do password-based auth
117                     class Password
118                     # This is the name of the field in the users table with the
119                     # password stored in it
120                     password_field password
121                     # Switch to more secure hashed passwords
122                     password_type  hashed
123                     # Use the SHA-1 hashing algorithm
124                     password_hash_type SHA-1
125                 </credential>
126                 <store>
127                     # Use DBIC to retrieve username, password & role information
128                     class DBIx::Class
129                     # This is the model object created by Catalyst::Model::DBIC
130                     # from your schema (you created 'MyApp::Schema::User' but as
131                     # the Catalyst startup debug messages show, it was loaded as
132                     # 'MyApp::Model::DB::Users').
133                     # NOTE: Omit 'MyApp::Model' here just as you would when using
134                     # '$c->model("DB::Users)'
135                     user_class DB::Users
136                     # This is the name of a many_to_many relation in the users
137                     # object that points to the roles for that user
138                     role_relation  roles
139                     # This is the name of field in the roles table that contains
140                     # the role information
141                     role_field role
142                 </store>
143             </dbic>
144         </realms>
145     </authentication>
146
147
148 =head2 Add Role-Specific Logic to the "Book List" Template
149
150 Open C<root/src/books/list.tt2> in your editor and add the following
151 lines to the bottom of the file:
152
153     <p>Hello [% c.user.username %], you have the following roles:</p>
154     
155     <ul>
156       [% # Dump list of roles -%]
157       [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
158     </ul>
159     
160     <p>
161     [% # Add some simple role-specific logic to template %]
162     [% # Use $c->check_user_roles() to check authz -%]
163     [% IF c.check_user_roles('user') %]
164       [% # Give normal users a link for 'logout' %]
165       <a href="[% c.uri_for('/logout') %]">User Logout</a>
166     [% END %]
167     
168     [% # Can also use $c->user->check_roles() to check authz -%]
169     [% IF c.check_user_roles('admin') %]
170       [% # Give admin users a link for 'create' %]
171       <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
172     [% END %]
173     </p>
174
175 This code displays a different combination of links depending on the
176 roles assigned to the user.
177
178
179 =head2 Limit C<Books::add> to C<admin> Users
180
181 C<IF> statements in TT templates simply control the output that is sent
182 to the user's browser; it provides no real enforcement (if users know or
183 guess the appropriate URLs, they are still perfectly free to hit any
184 action within your application).  We need to enhance the controller
185 logic to wrap restricted actions with role-validation logic.
186
187 For example, we might want to restrict the "formless create" action to
188 admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
189 updating C<url_create> to match the following code:
190
191     =head2 url_create
192     
193     Create a book with the supplied title and rating,
194     with manual authorization
195     
196     =cut
197     
198     sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
199         # In addition to self & context, get the title, rating & author_id args
200         # from the URL.  Note that Catalyst automatically puts extra information
201         # after the "/<controller_name>/<action_name/" into @_
202         my ($self, $c, $title, $rating, $author_id) = @_;
203     
204         # Check the user's roles
205         if ($c->check_user_roles('admin')) {
206             # Call create() on the book model object. Pass the table
207             # columns/field values we want to set as hash values
208             my $book = $c->model('DB::Books')->create({
209                     title   => $title,
210                     rating  => $rating
211                 });
212     
213             # Add a record to the join table for this book, mapping to
214             # appropriate author
215             $book->add_to_book_authors({author_id => $author_id});
216             # Note: Above is a shortcut for this:
217             # $book->create_related('book_authors', {author_id => $author_id});
218     
219             # Assign the Book object to the stash for display in the view
220             $c->stash->{book} = $book;
221     
222             # This is a hack to disable XSUB processing in Data::Dumper
223             # (it's used in the view).  This is a work-around for a bug in
224             # the interaction of some versions or Perl, Data::Dumper & DBIC.
225             # You won't need this if you aren't using Data::Dumper (or if
226             # you are running DBIC 0.06001 or greater), but adding it doesn't
227             # hurt anything either.
228             $Data::Dumper::Useperl = 1;
229     
230             # Set the TT template to use
231             $c->stash->{template} = 'books/create_done.tt2';
232         } else {
233             # Provide very simple feedback to the user.
234             $c->response->body('Unauthorized!');
235         }
236     }
237
238
239 To add authorization, we simply wrap the main code of this method in an
240 C<if> statement that calls C<check_user_roles>.  If the user does not
241 have the appropriate permissions, they receive an "Unauthorized!"
242 message.  Note that we intentionally chose to display the message this
243 way to demonstrate that TT templates will not be used if the response
244 body has already been set.  In reality you would probably want to use a
245 technique that maintains the visual continuity of your template layout
246 (for example, using the "status" or "error" message feature added in
247 Part 3 or C<detach> to an action that shows an "unauthorized" page).
248
249 B<TIP>: If you want to keep your existing C<url_create> method, you can
250 create a new copy and comment out the original by making it look like a
251 Pod comment.  For example, put something like C<=begin> before 
252 C<sub add : Local {> and C<=end> after the closing C<}>.
253
254
255 =head2 Try Out Authentication And Authorization
256
257 Press C<Ctrl-C> to kill the previous server instance (if it's still
258 running) and restart it:
259
260     $ script/myapp_server.pl
261
262 Now trying going to L<http://localhost:3000/books/list> and you should 
263 be taken to the login page (you might have to C<Shift+Reload> or 
264 C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book 
265 list page).  Try logging in with both C<test01> and C<test02> (both 
266 use a password of C<mypass>) and notice how the roles information 
267 updates at the bottom of the "Book List" page. Also try the "User Logout"
268 link on the book list page.
269
270 Now the "url_create" URL will work if you are already logged in as user
271 C<test01>, but receive an authorization failure if you are logged in as
272 C<test02>.  Try:
273
274     http://localhost:3000/books/url_create/test/1/6
275
276 while logged in as each user.  Use one of the "logout" links (or go to 
277 L<http://localhost:3000/logout> in your browser directly) when you are 
278 done.
279
280
281 =head1 ENABLE ACL-BASED AUTHORIZATION
282
283 This section takes a brief look at how the
284 L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
285 plugin can automate much of the work required to perform role-based
286 authorization in a Catalyst application.
287
288
289 =head2 Add the C<Catalyst::Plugin::Authorization::ACL> Plugin
290
291 Open C<lib/MyApp.pm> in your editor and add the following plugin to the
292 C<__PACKAGE__-E<gt>setup> statement:
293
294     Authorization::ACL
295
296 Note that the remaining C<use Catalyst> plugins from earlier sections
297 are not shown here, but they should still be included.
298
299
300 =head2 Add ACL Rules to the Application Class
301
302 Open C<lib/MyApp.pm> in your editor and add the following B<BELOW> the
303 C<__PACKAGE__-E<gt>setup> statement:
304
305     # Authorization::ACL Rules
306     __PACKAGE__->deny_access_unless(
307             "/books/form_create",
308             [qw/admin/],
309         );
310     __PACKAGE__->deny_access_unless(
311             "/books/form_create_do",
312             [qw/admin/],
313         );
314     __PACKAGE__->allow_access_if(
315             "/books/delete",
316             [qw/user admin/],
317         );
318
319 Each of the three statements above comprises an ACL plugin "rule".  The
320 first two rules only allow admin-level users to create new books using
321 the form (both the form itself and the data submission logic are
322 protected).  The third statement allows both users and admins to delete
323 books; letting users delete but not create book entries may sound odd in
324 the "real world", but this is just an example.  The C</books/url_create>
325 action will continue to be protected by the "manually configured"
326 authorization created earlier in this part of the tutorial.
327
328 The ACL plugin permits you to apply allow/deny logic in a variety of
329 ways.  The following provides a basic overview of the capabilities:
330
331 =over 4
332
333 =item *
334
335 The ACL plugin only operates on the Catalyst "private namespace".  You
336 are using the private namespace when you use C<Local> actions.  C<Path>,
337 C<Regex>, and C<Global> allow you to specify actions where the path and
338 the namespace differ -- the ACL plugin will not work in these cases.
339
340 =item *
341
342 Each rule is expressed in a separate
343 C<__PACKAGE__-E<gt>deny_access_unless()> or
344 C<__PACKAGE__-E<gt>allow_access_if()> line (there are several other
345 methods that can be used for more complex policies, see the C<METHODS>
346 portion of the
347 L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
348 documentation for more details).
349
350 =item *
351
352 Each rule can contain multiple roles but only a single path.
353
354 =item *
355
356 The rules are tried in order (with the "most specific" rules tested
357 first), and processing stops at the first "match" where an allow or deny
358 is specified.  Rules "fall through" if there is not a "match" (where a
359 "match" means the user has the specified role).  If a "match" is found,
360 then processing stops there and the appropriate allow/deny action is
361 taken.
362
363 =item *
364
365 If none of the rules match, then access is allowed.
366
367 =item *
368
369 The rules currently need to be specified in the application class
370 C<lib\MyApp.pm> B<after> the C<__PACKAGE__-E<gt>setup;> line.
371
372 =back
373
374
375 =head2 Add a Method to Handle Access Violations
376
377 By default,
378 L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
379 throws an exception when authorization fails.  This will take the user
380 to the Catalyst debug screen, or a "Please come back later" message if
381 you are not using the C<-Debug> flag. This step uses the
382 C<access_denied> method in order to provide more appropriate feedback to
383 the user.
384
385 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
386 following method:
387
388     =head2 access_denied
389     
390     Handle Catalyst::Plugin::Authorization::ACL access denied exceptions
391     
392     =cut
393     
394     sub access_denied : Private {
395         my ($self, $c) = @_;
396     
397         # Set the error message
398         $c->stash->{error_msg} = 'Unauthorized!';
399     
400         # Display the list
401         $c->forward('list');
402     }
403
404 Then run the Catalyst development server script:
405
406     $ script/myapp_server.pl
407
408 Log in as C<test02>.  Once at the book list, click the "Create" link
409 to try the C<form_create> action.  You should receive a red
410 "Unauthorized!"  error message at the top of the list.  (Note that in
411 the example code the "Admin Create" link code in C<root/src/books/list.tt2>
412 is inside an C<IF> statement that only displays the list to
413 admin-level users.)  If you log in as C<test01> you should be able to
414 view the C<form_create> form and add a new book.
415
416 Use one of the 'Logout' links (or go to the
417 L<http://localhost:3000/logout> URL directly) when you are done.
418
419
420 =head1 AUTHOR
421
422 Kennedy Clark, C<hkclark@gmail.com>
423
424 Please report any errors, issues or suggestions to the author.  The
425 most recent version of the Catalyst Tutorial can be found at
426 L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
427
428 Copyright 2006-2008, Kennedy Clark, under Creative Commons License
429 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
430