Update year on copyright
[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:
aa7ff325 81 # Load plugins
82 use Catalyst qw/
83 -Debug
84 ConfigLoader
85 Static::Simple
87 StackTrace
89 Authentication
90 Authorization::Roles
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
6c0f71ee 175 # Assign the Book object to the stash and set template
176 $c->stash(book => $book,
aa7ff325 177 template => 'books/create_done.tt2');
d442cc9f 178 } else {
e075db0c 179 # Provide very simple feedback to the user.
d442cc9f 180 $c->response->body('Unauthorized!');
181 }
182 }
185To add authorization, we simply wrap the main code of this method in an
186C<if> statement that calls C<check_user_roles>. If the user does not
187have the appropriate permissions, they receive an "Unauthorized!"
188message. Note that we intentionally chose to display the message this
189way to demonstrate that TT templates will not be used if the response
190body has already been set. In reality you would probably want to use a
191technique that maintains the visual continuity of your template layout
192(for example, using the "status" or "error" message feature added in
4b4d3884 193Chapter 3 or C<detach> to an action that shows an "unauthorized" page).
d442cc9f 194
195B<TIP>: If you want to keep your existing C<url_create> method, you can
196create a new copy and comment out the original by making it look like a
e075db0c 197Pod comment. For example, put something like C<=begin> before
198C<sub add : Local {> and C<=end> after the closing C<}>.
d442cc9f 199
1390ef0e 200
d442cc9f 201=head2 Try Out Authentication And Authorization
6c0f71ee 203Make sure the development server is running:
d442cc9f 204
6c0f71ee 205 $ script/myapp_server.pl -r
d442cc9f 206
1390ef0e 207Now trying going to L<http://localhost:3000/books/list> and you should
208be taken to the login page (you might have to C<Shift+Reload> or
fbbb9084 209C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book
1390ef0e 210list page). Try logging in with both C<test01> and C<test02> (both
211use a password of C<mypass>) and notice how the roles information
fbbb9084 212updates at the bottom of the "Book List" page. Also try the "User Logout"
1390ef0e 213link on the book list page.
d442cc9f 214
215Now the "url_create" URL will work if you are already logged in as user
216C<test01>, but receive an authorization failure if you are logged in as
217C<test02>. Try:
219 http://localhost:3000/books/url_create/test/1/6
fbbb9084 221while logged in as each user. Use one of the "logout" links (or go to
1390ef0e 222L<http://localhost:3000/logout> in your browser directly) when you are
d442cc9f 223done.
d442cc9f 227
acbd7bdd 228Hopefully it's fairly obvious that adding detailed permission checking
229logic to our controllers and view templates isn't a very clean or
230scalable way to build role-based permissions into out application. As
231with many other aspects of MVC web development, the goal is to have
232your controllers and views be an "thin" as possible, with all of the
233"fancy business logic" built into your model.
d442cc9f 234
acbd7bdd 235For example, let's add a method to our C<Books.pm> Result Class to
236check if a user is allowed to delete a book. Open
3b1fa91b 237C<lib/MyApp/Schema/Result/Book.pm> and add the following method
acbd7bdd 238(be sure to add it below the "C<DO NOT MODIFY ...>" line):
d442cc9f 239
acbd7bdd 240 =head2 delete_allowed_by
242 Can the specified user delete the current book?
244 =cut
246 sub delete_allowed_by {
247 my ($self, $user) = @_;
249 # Only allow delete if user has 'admin' role
250 return $user->has_role('admin');
251 }
d442cc9f 252
acbd7bdd 253Here we call a C<has_role> method on our user object, so we should add
254this method to our Result Class. Open
3b1fa91b 255C<lib/MyApp/Schema/Result/User.pm> and add the following method below
8e571e49 256the "C<DO NOT MODIFY ...>" line:
d442cc9f 257
aa7ff325 258 =head2 has_role
acbd7bdd 259
260 Check if a user has the specified role
262 =cut
8e571e49 264 use Perl6::Junction qw/any/;
acbd7bdd 265 sub has_role {
266 my ($self, $role) = @_;
268 # Does this user posses the required role?
269 return any(map { $_->role } $self->roles) eq $role;
270 }
d442cc9f 271
acbd7bdd 272Now we need to add some enforcement inside our controller. Open
273C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
274match the following code:
d442cc9f 275
acbd7bdd 276 =head2 delete
1390ef0e 277
acbd7bdd 278 Delete a book
1390ef0e 279
d442cc9f 280 =cut
1390ef0e 281
acbd7bdd 282 sub delete :Chained('object') :PathPart('delete') :Args(0) {
d442cc9f 283 my ($self, $c) = @_;
1390ef0e 284
acbd7bdd 285 # Check permissions
286 $c->detach('/error_noperms')
287 unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
289 # Use the book object saved by 'object' and delete it along
290 # with related 'book_authors' entries
291 $c->stash->{object}->delete;
1390ef0e 292
acbd7bdd 293 # Use 'flash' to save information across requests until it's read
294 $c->flash->{status_msg} = "Book deleted";
296 # Redirect the user back to the list page
297 $c->response->redirect($c->uri_for($self->action_for('list')));
2a6eb5f9 298 }
acbd7bdd 299
300Here, we C<detach> to an error page if the user is lacking the
301appropriate permissions. For this to work, we need to make
302arrangements for the '/error_noperms' action to work. Open
303C<lib/MyApp/Controller/Root.pm> and add this method:
305 =head2 error_noperms
307 Permissions error screen
309 =cut
614b484c 311 sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
acbd7bdd 312 my ($self, $c) = @_;
6c0f71ee 314 $c->stash(template => 'error_noperms.tt2');
d442cc9f 315 }
acbd7bdd 317And also add the template file by putting the following text into
320 <span class="error">Permission Denied</span>
acbd7bdd 322Log in as C<test01> and create several new books using the C<url_create>
325 http://localhost:3000/books/url_create/Test/1/4
327Then, while still logged in as C<test01>, click the "Delete" link next
328to one of these books. The book should be removed and you should see
329the usual green "Book deleted" message. Next, click the "User Logout"
330link and log back in as C<test02>. Now try deleting one of the books.
331You should be taken to the red "Permission Denied" message on our
332error page.
d442cc9f 333
4feda61c 334Use one of the 'Logout' links (or go to the
d442cc9f 335L<http://localhost:3000/logout> URL directly) when you are done.
338=head1 AUTHOR
340Kennedy Clark, C<hkclark@gmail.com>
342Please report any errors, issues or suggestions to the author. The
343most recent version of the Catalyst Tutorial can be found at
59884771 344L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
d442cc9f 345
4768184b 346Copyright 2006-2010, Kennedy Clark, under Creative Commons License
95674086 347(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
d442cc9f 348