minor change to chapter 6 POD
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / 06_Authorization.pod
d442cc9f 1=head1 NAME
3ab6187c 3Catalyst::Manual::Tutorial::06_Authorization - Catalyst Tutorial - Chapter 6: Authorization
d442cc9f 4
6=head1 OVERVIEW
4b4d3884 8This is B<Chapter 6 of 10> for the Catalyst tutorial.
d442cc9f 9
10L<Tutorial Overview|Catalyst::Manual::Tutorial>
12=over 4
14=item 1
3ab6187c 16L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
d442cc9f 17
18=item 2
3ab6187c 20L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
d442cc9f 21
22=item 3
3ab6187c 24L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
d442cc9f 25
26=item 4
3ab6187c 28L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
d442cc9f 29
30=item 5
3ab6187c 32L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
d442cc9f 33
34=item 6
3ab6187c 36B<06_Authorization>
d442cc9f 37
38=item 7
3ab6187c 40L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
d442cc9f 41
42=item 8
3ab6187c 44L<Testing|Catalyst::Manual::Tutorial::08_Testing>
d442cc9f 45
46=item 9
3ab6187c 48L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
3533daff 49
50=item 10
3ab6187c 52L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
d442cc9f 53
d442cc9f 57=head1 DESCRIPTION
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
3ab6187c 68L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
1390ef0e 69
d442cc9f 70
acbd7bdd 73In this section you learn the basics of how authorization works under
d442cc9f 75
1390ef0e 76
d442cc9f 77=head2 Update Plugins to Include Support for Authorization
79Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
acbd7bdd 81 # Load plugins
2a6eb5f9 82 use Catalyst qw/
83 -Debug
3b1fa91b 84 ConfigLoader
85 Static::Simple
acbd7bdd 86
3b1fa91b 87 StackTrace
acbd7bdd 88
3b1fa91b 89 Authentication
90 Authorization::Roles
acbd7bdd 91
3b1fa91b 92 Session
93 Session::Store::FastMmap
94 Session::State::Cookie
95 /;
d442cc9f 96
a467a714 97Once again, include this additional plugin as a new dependency in
98the Makefile.PL file like this:
3b1fa91b 99
78b0b5f6 100 requires 'Catalyst::Plugin::Authorization::Roles';
d442cc9f 101
2a6eb5f9 102
d442cc9f 103=head2 Add Role-Specific Logic to the "Book List" Template
105Open C<root/src/books/list.tt2> in your editor and add the following
106lines to the bottom of the file:
acbd7bdd 108 ...
8a7c5151 109 <p>Hello [% c.user.username %], you have the following roles:</p>
1390ef0e 110
d442cc9f 111 <ul>
112 [% # Dump list of roles -%]
2a6eb5f9 113 [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
d442cc9f 114 </ul>
1390ef0e 115
d442cc9f 116 <p>
117 [% # Add some simple role-specific logic to template %]
118 [% # Use $c->check_user_roles() to check authz -%]
8a7c5151 119 [% IF c.check_user_roles('user') %]
d442cc9f 120 [% # Give normal users a link for 'logout' %]
e075db0c 121 <a href="[% c.uri_for('/logout') %]">User Logout</a>
d442cc9f 122 [% END %]
1390ef0e 123
d442cc9f 124 [% # Can also use $c->user->check_roles() to check authz -%]
8a7c5151 125 [% IF c.check_user_roles('admin') %]
d442cc9f 126 [% # Give admin users a link for 'create' %]
0416017e 127 <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
d442cc9f 128 [% END %]
129 </p>
131This code displays a different combination of links depending on the
444d6b27 132roles assigned to the user.
d442cc9f 133
1390ef0e 134
8a472b34 135=head2 Limit Books::add to 'admin' Users
d442cc9f 136
137C<IF> statements in TT templates simply control the output that is sent
138to the user's browser; it provides no real enforcement (if users know or
139guess the appropriate URLs, they are still perfectly free to hit any
140action within your application). We need to enhance the controller
141logic to wrap restricted actions with role-validation logic.
143For example, we might want to restrict the "formless create" action to
144admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
145updating C<url_create> to match the following code:
147 =head2 url_create
1390ef0e 148
d442cc9f 149 Create a book with the supplied title and rating,
150 with manual authorization
1390ef0e 151
d442cc9f 152 =cut
1390ef0e 153
e075db0c 154 sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
d442cc9f 155 # In addition to self & context, get the title, rating & author_id args
156 # from the URL. Note that Catalyst automatically puts extra information
157 # after the "/<controller_name>/<action_name/" into @_
158 my ($self, $c, $title, $rating, $author_id) = @_;
1390ef0e 159
d442cc9f 160 # Check the user's roles
161 if ($c->check_user_roles('admin')) {
905a3a26 162 # Call create() on the book model object. Pass the table
d442cc9f 163 # columns/field values we want to set as hash values
3b1fa91b 164 my $book = $c->model('DB::Book')->create({
d442cc9f 165 title => $title,
166 rating => $rating
167 });
1390ef0e 168
905a3a26 169 # Add a record to the join table for this book, mapping to
d442cc9f 170 # appropriate author
2a6eb5f9 171 $book->add_to_book_authors({author_id => $author_id});
d442cc9f 172 # Note: Above is a shortcut for this:
2a6eb5f9 173 # $book->create_related('book_authors', {author_id => $author_id});
1390ef0e 174
d442cc9f 175 # Assign the Book object to the stash for display in the view
176 $c->stash->{book} = $book;
1390ef0e 177
d442cc9f 178 # Set the TT template to use
179 $c->stash->{template} = 'books/create_done.tt2';
180 } else {
e075db0c 181 # Provide very simple feedback to the user.
d442cc9f 182 $c->response->body('Unauthorized!');
183 }
184 }
187To add authorization, we simply wrap the main code of this method in an
188C<if> statement that calls C<check_user_roles>. If the user does not
189have the appropriate permissions, they receive an "Unauthorized!"
190message. Note that we intentionally chose to display the message this
191way to demonstrate that TT templates will not be used if the response
192body has already been set. In reality you would probably want to use a
193technique that maintains the visual continuity of your template layout
194(for example, using the "status" or "error" message feature added in
4b4d3884 195Chapter 3 or C<detach> to an action that shows an "unauthorized" page).
d442cc9f 196
197B<TIP>: If you want to keep your existing C<url_create> method, you can
198create a new copy and comment out the original by making it look like a
e075db0c 199Pod comment. For example, put something like C<=begin> before
200C<sub add : Local {> and C<=end> after the closing C<}>.
d442cc9f 201
1390ef0e 202
d442cc9f 203=head2 Try Out Authentication And Authorization
205Press C<Ctrl-C> to kill the previous server instance (if it's still
206running) and restart it:
208 $ script/myapp_server.pl
1390ef0e 210Now trying going to L<http://localhost:3000/books/list> and you should
211be taken to the login page (you might have to C<Shift+Reload> or
fbbb9084 212C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book
1390ef0e 213list page). Try logging in with both C<test01> and C<test02> (both
214use a password of C<mypass>) and notice how the roles information
fbbb9084 215updates at the bottom of the "Book List" page. Also try the "User Logout"
1390ef0e 216link on the book list page.
d442cc9f 217
218Now the "url_create" URL will work if you are already logged in as user
219C<test01>, but receive an authorization failure if you are logged in as
220C<test02>. Try:
222 http://localhost:3000/books/url_create/test/1/6
fbbb9084 224while logged in as each user. Use one of the "logout" links (or go to
1390ef0e 225L<http://localhost:3000/logout> in your browser directly) when you are
d442cc9f 226done.
d442cc9f 230
acbd7bdd 231Hopefully it's fairly obvious that adding detailed permission checking
232logic to our controllers and view templates isn't a very clean or
233scalable way to build role-based permissions into out application. As
234with many other aspects of MVC web development, the goal is to have
235your controllers and views be an "thin" as possible, with all of the
236"fancy business logic" built into your model.
d442cc9f 237
acbd7bdd 238For example, let's add a method to our C<Books.pm> Result Class to
239check if a user is allowed to delete a book. Open
3b1fa91b 240C<lib/MyApp/Schema/Result/Book.pm> and add the following method
acbd7bdd 241(be sure to add it below the "C<DO NOT MODIFY ...>" line):
d442cc9f 242
acbd7bdd 243 =head2 delete_allowed_by
245 Can the specified user delete the current book?
247 =cut
249 sub delete_allowed_by {
250 my ($self, $user) = @_;
252 # Only allow delete if user has 'admin' role
253 return $user->has_role('admin');
254 }
d442cc9f 255
acbd7bdd 256Here we call a C<has_role> method on our user object, so we should add
257this method to our Result Class. Open
3b1fa91b 258C<lib/MyApp/Schema/Result/User.pm> and add the following method below
8e571e49 259the "C<DO NOT MODIFY ...>" line:
d442cc9f 260
acbd7bdd 261 =head 2 has_role
263 Check if a user has the specified role
265 =cut
8e571e49 267 use Perl6::Junction qw/any/;
acbd7bdd 268 sub has_role {
269 my ($self, $role) = @_;
271 # Does this user posses the required role?
272 return any(map { $_->role } $self->roles) eq $role;
273 }
d442cc9f 274
acbd7bdd 275Now we need to add some enforcement inside our controller. Open
276C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
277match the following code:
d442cc9f 278
acbd7bdd 279 =head2 delete
1390ef0e 280
acbd7bdd 281 Delete a book
1390ef0e 282
d442cc9f 283 =cut
1390ef0e 284
acbd7bdd 285 sub delete :Chained('object') :PathPart('delete') :Args(0) {
d442cc9f 286 my ($self, $c) = @_;
1390ef0e 287
acbd7bdd 288 # Check permissions
289 $c->detach('/error_noperms')
290 unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
292 # Use the book object saved by 'object' and delete it along
293 # with related 'book_authors' entries
294 $c->stash->{object}->delete;
1390ef0e 295
acbd7bdd 296 # Use 'flash' to save information across requests until it's read
297 $c->flash->{status_msg} = "Book deleted";
299 # Redirect the user back to the list page
300 $c->response->redirect($c->uri_for($self->action_for('list')));
2a6eb5f9 301 }
acbd7bdd 302
303Here, we C<detach> to an error page if the user is lacking the
304appropriate permissions. For this to work, we need to make
305arrangements for the '/error_noperms' action to work. Open
306C<lib/MyApp/Controller/Root.pm> and add this method:
308 =head2 error_noperms
310 Permissions error screen
312 =cut
614b484c 314 sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
acbd7bdd 315 my ($self, $c) = @_;
317 $c->stash->{template} = 'error_noperms.tt2';
d442cc9f 318 }
acbd7bdd 320And also add the template file by putting the following text into
323 <span class="error">Permission Denied</span>
905a3a26 325Then run the Catalyst development server script:
d442cc9f 326
327 $ script/myapp_server.pl
acbd7bdd 329Log in as C<test01> and create several new books using the C<url_create>
332 http://localhost:3000/books/url_create/Test/1/4
334Then, while still logged in as C<test01>, click the "Delete" link next
335to one of these books. The book should be removed and you should see
336the usual green "Book deleted" message. Next, click the "User Logout"
337link and log back in as C<test02>. Now try deleting one of the books.
338You should be taken to the red "Permission Denied" message on our
339error page.
d442cc9f 340
4feda61c 341Use one of the 'Logout' links (or go to the
d442cc9f 342L<http://localhost:3000/logout> URL directly) when you are done.
345=head1 AUTHOR
347Kennedy Clark, C<hkclark@gmail.com>
349Please report any errors, issues or suggestions to the author. The
350most recent version of the Catalyst Tutorial can be found at
59884771 351L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
d442cc9f 352
45c7830f 353Copyright 2006-2008, Kennedy Clark, under Creative Commons License
95674086 354(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
d442cc9f 355