Fix minor typo
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / Authorization.pod
CommitLineData
d442cc9f 1=head1 NAME
2
4b4d3884 3Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Chapter 6: Authorization
d442cc9f 4
5
6=head1 OVERVIEW
7
4b4d3884 8This is B<Chapter 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
4b4d3884 59This chapter of the tutorial adds role-based authorization to the
60existing authentication implemented in Chapter 5. It provides simple
acbd7bdd 61examples of how to use roles in both TT templates and controller
62actions. The first half looks at basic authorization concepts. The
63second half looks at how moving your authorization code to your model
64can simplify your code and make things easier to maintain.
d442cc9f 65
66You can checkout the source code for this example from the catalyst
67subversion repository as per the instructions in
1390ef0e 68L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
69
d442cc9f 70
71=head1 BASIC AUTHORIZATION
72
acbd7bdd 73In this section you learn the basics of how authorization works under
74Catalyst.
d442cc9f 75
1390ef0e 76
d442cc9f 77=head2 Update Plugins to Include Support for Authorization
78
79Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
80
acbd7bdd 81 # Load plugins
82 use Catalyst qw/-Debug
83 ConfigLoader
84 Static::Simple
85
86 StackTrace
87
88 Authentication
89 Authorization::Roles
90
91 Session
92 Session::Store::FastMmap
93 Session::State::Cookie
94 /;
d442cc9f 95
94d8da41 96B<Note:> As discussed in MoreCatalystBasics, different versions of
97C<Catalyst::Devel> have used a variety of methods to load the plugins.
acbd7bdd 98You can put the plugins in the C<use Catalyst> statement if you
99prefer.
94d8da41 100
d442cc9f 101
102=head2 Add Config Information for Authorization
103
905a3a26 104Edit C<myapp.conf> and update it to match the following (the
3533daff 105C<role_relation> and C<role_field> definitions are new):
d442cc9f 106
1390ef0e 107 # rename this file to MyApp.yml and put a : in front of "name" if
108 # you want to use yaml like in old versions of Catalyst
c010ae0d 109 name MyApp
110 <authentication>
111 default_realm dbic
112 <realms>
113 <dbic>
114 <credential>
3533daff 115 # Note this first definition would be the same as setting
116 # __PACKAGE__->config->{authentication}->{realms}->{dbic}
905a3a26 117 # ->{credential} = 'Password' in lib/MyApp.pm
3533daff 118 #
119 # Specify that we are going to do password-based auth
c010ae0d 120 class Password
3533daff 121 # This is the name of the field in the users table with the
122 # password stored in it
c010ae0d 123 password_field password
d0496197 124 # Switch to more secure hashed passwords
125 password_type hashed
126 # Use the SHA-1 hashing algorithm
127 password_hash_type SHA-1
128 </credential>
c010ae0d 129 <store>
3533daff 130 # Use DBIC to retrieve username, password & role information
c010ae0d 131 class DBIx::Class
905a3a26 132 # This is the model object created by Catalyst::Model::DBIC
acbd7bdd 133 # from your schema (you created 'MyApp::Schema::Result::User'
134 # but as the Catalyst startup debug messages show, it was
135 # loaded as 'MyApp::Model::DB::Users').
905a3a26 136 # NOTE: Omit 'MyApp::Model' here just as you would when using
d0496197 137 # '$c->model("DB::Users)'
138 user_class DB::Users
3533daff 139 # This is the name of a many_to_many relation in the users
140 # object that points to the roles for that user
c010ae0d 141 role_relation roles
3533daff 142 # This is the name of field in the roles table that contains
143 # the role information
c010ae0d 144 role_field role
d0496197 145 </store>
146 </dbic>
147 </realms>
148 </authentication>
d442cc9f 149
150
151=head2 Add Role-Specific Logic to the "Book List" Template
152
153Open C<root/src/books/list.tt2> in your editor and add the following
154lines to the bottom of the file:
155
acbd7bdd 156 ...
8a7c5151 157 <p>Hello [% c.user.username %], you have the following roles:</p>
1390ef0e 158
d442cc9f 159 <ul>
160 [% # Dump list of roles -%]
8a7c5151 161 [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
d442cc9f 162 </ul>
1390ef0e 163
d442cc9f 164 <p>
165 [% # Add some simple role-specific logic to template %]
166 [% # Use $c->check_user_roles() to check authz -%]
8a7c5151 167 [% IF c.check_user_roles('user') %]
d442cc9f 168 [% # Give normal users a link for 'logout' %]
e075db0c 169 <a href="[% c.uri_for('/logout') %]">User Logout</a>
d442cc9f 170 [% END %]
1390ef0e 171
d442cc9f 172 [% # Can also use $c->user->check_roles() to check authz -%]
8a7c5151 173 [% IF c.check_user_roles('admin') %]
d442cc9f 174 [% # Give admin users a link for 'create' %]
0416017e 175 <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
d442cc9f 176 [% END %]
177 </p>
178
179This code displays a different combination of links depending on the
180roles assigned to the user.
181
1390ef0e 182
d442cc9f 183=head2 Limit C<Books::add> to C<admin> Users
184
185C<IF> statements in TT templates simply control the output that is sent
186to the user's browser; it provides no real enforcement (if users know or
187guess the appropriate URLs, they are still perfectly free to hit any
188action within your application). We need to enhance the controller
189logic to wrap restricted actions with role-validation logic.
190
191For example, we might want to restrict the "formless create" action to
192admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
193updating C<url_create> to match the following code:
194
195 =head2 url_create
1390ef0e 196
d442cc9f 197 Create a book with the supplied title and rating,
198 with manual authorization
1390ef0e 199
d442cc9f 200 =cut
1390ef0e 201
e075db0c 202 sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
d442cc9f 203 # In addition to self & context, get the title, rating & author_id args
204 # from the URL. Note that Catalyst automatically puts extra information
205 # after the "/<controller_name>/<action_name/" into @_
206 my ($self, $c, $title, $rating, $author_id) = @_;
1390ef0e 207
d442cc9f 208 # Check the user's roles
209 if ($c->check_user_roles('admin')) {
905a3a26 210 # Call create() on the book model object. Pass the table
d442cc9f 211 # columns/field values we want to set as hash values
d0496197 212 my $book = $c->model('DB::Books')->create({
d442cc9f 213 title => $title,
214 rating => $rating
215 });
1390ef0e 216
905a3a26 217 # Add a record to the join table for this book, mapping to
d442cc9f 218 # appropriate author
219 $book->add_to_book_authors({author_id => $author_id});
220 # Note: Above is a shortcut for this:
221 # $book->create_related('book_authors', {author_id => $author_id});
1390ef0e 222
d442cc9f 223 # Assign the Book object to the stash for display in the view
224 $c->stash->{book} = $book;
1390ef0e 225
d442cc9f 226 # This is a hack to disable XSUB processing in Data::Dumper
227 # (it's used in the view). This is a work-around for a bug in
228 # the interaction of some versions or Perl, Data::Dumper & DBIC.
229 # You won't need this if you aren't using Data::Dumper (or if
905a3a26 230 # you are running DBIC 0.06001 or greater), but adding it doesn't
d442cc9f 231 # hurt anything either.
232 $Data::Dumper::Useperl = 1;
1390ef0e 233
d442cc9f 234 # Set the TT template to use
235 $c->stash->{template} = 'books/create_done.tt2';
236 } else {
e075db0c 237 # Provide very simple feedback to the user.
d442cc9f 238 $c->response->body('Unauthorized!');
239 }
240 }
241
242
243To add authorization, we simply wrap the main code of this method in an
244C<if> statement that calls C<check_user_roles>. If the user does not
245have the appropriate permissions, they receive an "Unauthorized!"
246message. Note that we intentionally chose to display the message this
247way to demonstrate that TT templates will not be used if the response
248body has already been set. In reality you would probably want to use a
249technique that maintains the visual continuity of your template layout
250(for example, using the "status" or "error" message feature added in
4b4d3884 251Chapter 3 or C<detach> to an action that shows an "unauthorized" page).
d442cc9f 252
253B<TIP>: If you want to keep your existing C<url_create> method, you can
254create a new copy and comment out the original by making it look like a
e075db0c 255Pod comment. For example, put something like C<=begin> before
256C<sub add : Local {> and C<=end> after the closing C<}>.
d442cc9f 257
1390ef0e 258
d442cc9f 259=head2 Try Out Authentication And Authorization
260
261Press C<Ctrl-C> to kill the previous server instance (if it's still
262running) and restart it:
263
264 $ script/myapp_server.pl
265
1390ef0e 266Now trying going to L<http://localhost:3000/books/list> and you should
267be taken to the login page (you might have to C<Shift+Reload> or
fbbb9084 268C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book
1390ef0e 269list page). Try logging in with both C<test01> and C<test02> (both
270use a password of C<mypass>) and notice how the roles information
fbbb9084 271updates at the bottom of the "Book List" page. Also try the "User Logout"
1390ef0e 272link on the book list page.
d442cc9f 273
274Now the "url_create" URL will work if you are already logged in as user
275C<test01>, but receive an authorization failure if you are logged in as
276C<test02>. Try:
277
278 http://localhost:3000/books/url_create/test/1/6
279
fbbb9084 280while logged in as each user. Use one of the "logout" links (or go to
1390ef0e 281L<http://localhost:3000/logout> in your browser directly) when you are
d442cc9f 282done.
283
284
acbd7bdd 285=head1 ENABLE MODEL-BASED AUTHORIZATION
d442cc9f 286
acbd7bdd 287Hopefully it's fairly obvious that adding detailed permission checking
288logic to our controllers and view templates isn't a very clean or
289scalable way to build role-based permissions into out application. As
290with many other aspects of MVC web development, the goal is to have
291your controllers and views be an "thin" as possible, with all of the
292"fancy business logic" built into your model.
d442cc9f 293
acbd7bdd 294For example, let's add a method to our C<Books.pm> Result Class to
295check if a user is allowed to delete a book. Open
296C<lib/MyApp/Schema/Result/Books.pm> and add the following method
297(be sure to add it below the "C<DO NOT MODIFY ...>" line):
d442cc9f 298
acbd7bdd 299 =head2 delete_allowed_by
300
301 Can the specified user delete the current book?
302
303 =cut
304
305 sub delete_allowed_by {
306 my ($self, $user) = @_;
307
308 # Only allow delete if user has 'admin' role
309 return $user->has_role('admin');
310 }
d442cc9f 311
acbd7bdd 312Here we call a C<has_role> method on our user object, so we should add
313this method to our Result Class. Open
314C<lib/MyApp/Schema/Result/Users.pm> and add this near the top:
d442cc9f 315
acbd7bdd 316 use Perl6::Junction qw/any/;
1390ef0e 317
acbd7bdd 318And then add the following method below the "C<DO NOT MODIFY ...>"
319line:
d442cc9f 320
acbd7bdd 321 =head 2 has_role
322
323 Check if a user has the specified role
324
325 =cut
326
327 sub has_role {
328 my ($self, $role) = @_;
329
330 # Does this user posses the required role?
331 return any(map { $_->role } $self->roles) eq $role;
332 }
d442cc9f 333
acbd7bdd 334Now we need to add some enforcement inside our controller. Open
335C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
336match the following code:
d442cc9f 337
acbd7bdd 338 =head2 delete
1390ef0e 339
acbd7bdd 340 Delete a book
1390ef0e 341
d442cc9f 342 =cut
1390ef0e 343
acbd7bdd 344 sub delete :Chained('object') :PathPart('delete') :Args(0) {
d442cc9f 345 my ($self, $c) = @_;
1390ef0e 346
acbd7bdd 347 # Check permissions
348 $c->detach('/error_noperms')
349 unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
350
351 # Use the book object saved by 'object' and delete it along
352 # with related 'book_authors' entries
353 $c->stash->{object}->delete;
1390ef0e 354
acbd7bdd 355 # Use 'flash' to save information across requests until it's read
356 $c->flash->{status_msg} = "Book deleted";
357
358 # Redirect the user back to the list page
359 $c->response->redirect($c->uri_for($self->action_for('list')));
360 }
361
362Here, we C<detach> to an error page if the user is lacking the
363appropriate permissions. For this to work, we need to make
364arrangements for the '/error_noperms' action to work. Open
365C<lib/MyApp/Controller/Root.pm> and add this method:
366
367 =head2 error_noperms
368
369 Permissions error screen
370
371 =cut
372
373 sub error_noperms :Chained('/') :PathPath('error_noperms') :Args(0) {
374 my ($self, $c) = @_;
375
376 $c->stash->{template} = 'error_noperms.tt2';
d442cc9f 377 }
378
acbd7bdd 379And also add the template file by putting the following text into
380C<root/src/error_noperms.tt2>:
381
382 <span class="error">Permission Denied</span>
383
905a3a26 384Then run the Catalyst development server script:
d442cc9f 385
386 $ script/myapp_server.pl
387
acbd7bdd 388Log in as C<test01> and create several new books using the C<url_create>
389feature:
390
391 http://localhost:3000/books/url_create/Test/1/4
392
393Then, while still logged in as C<test01>, click the "Delete" link next
394to one of these books. The book should be removed and you should see
395the usual green "Book deleted" message. Next, click the "User Logout"
396link and log back in as C<test02>. Now try deleting one of the books.
397You should be taken to the red "Permission Denied" message on our
398error page.
d442cc9f 399
4feda61c 400Use one of the 'Logout' links (or go to the
d442cc9f 401L<http://localhost:3000/logout> URL directly) when you are done.
402
403
404=head1 AUTHOR
405
406Kennedy Clark, C<hkclark@gmail.com>
407
408Please report any errors, issues or suggestions to the author. The
409most recent version of the Catalyst Tutorial can be found at
82ab4bbf 410L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
d442cc9f 411
45c7830f 412Copyright 2006-2008, Kennedy Clark, under Creative Commons License
95674086 413(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
d442cc9f 414