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