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