link fixes
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / 06_Authorization.pod
CommitLineData
d442cc9f 1=head1 NAME
2
3ab6187c 3Catalyst::Manual::Tutorial::06_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
3ab6187c 16L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
d442cc9f 17
18=item 2
19
3ab6187c 20L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
d442cc9f 21
22=item 3
23
3ab6187c 24L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
d442cc9f 25
26=item 4
27
3ab6187c 28L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
d442cc9f 29
30=item 5
31
3ab6187c 32L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
d442cc9f 33
34=item 6
35
3ab6187c 36B<06_Authorization>
d442cc9f 37
38=item 7
39
3ab6187c 40L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
d442cc9f 41
42=item 8
43
3ab6187c 44L<Testing|Catalyst::Manual::Tutorial::08_Testing>
d442cc9f 45
46=item 9
47
3ab6187c 48L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
3533daff 49
50=item 10
51
3ab6187c 52L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
d442cc9f 53
54=back
55
56
d442cc9f 57=head1 DESCRIPTION
58
bf4d990b 59This chapter of the tutorial adds role-based authorization to the
333f9299 60existing authentication implemented in
61L<Chapter 5|Catalyst::Manual::Tutorial::05_Authentication>. It provides
62simple examples of how to use roles in both TT templates and controller
bf4d990b 63actions. The first half looks at basic authorization concepts. The
64second half looks at how moving your authorization code to your model
acbd7bdd 65can simplify your code and make things easier to maintain.
d442cc9f 66
477a6d5b 67Source code for the tutorial in included in the F</home/catalyst/Final>
68directory of the Tutorial Virtual machine (one subdirectory per
69chapter). There are also instructions for downloading the code in
2217b252 70L<Catalyst::Manual::Tutorial::01_Intro>.
1390ef0e 71
d442cc9f 72
73=head1 BASIC AUTHORIZATION
74
bf4d990b 75In this section you learn the basics of how authorization works under
acbd7bdd 76Catalyst.
d442cc9f 77
1390ef0e 78
d442cc9f 79=head2 Update Plugins to Include Support for Authorization
80
81Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
82
aa7ff325 83 # Load plugins
84 use Catalyst qw/
85 -Debug
86 ConfigLoader
87 Static::Simple
7ce05098 88
aa7ff325 89 StackTrace
7ce05098 90
aa7ff325 91 Authentication
92 Authorization::Roles
7ce05098 93
aa7ff325 94 Session
95455c74 95 Session::Store::File
aa7ff325 96 Session::State::Cookie
333f9299 97
98 StatusMessage
aa7ff325 99 /;
d442cc9f 100
bf4d990b 101Once again, include this additional plugin as a new dependency in the
102Makefile.PL file like this:
3b1fa91b 103
78b0b5f6 104 requires 'Catalyst::Plugin::Authorization::Roles';
d442cc9f 105
2a6eb5f9 106
d442cc9f 107=head2 Add Role-Specific Logic to the "Book List" Template
108
109Open C<root/src/books/list.tt2> in your editor and add the following
110lines to the bottom of the file:
111
acbd7bdd 112 ...
8a7c5151 113 <p>Hello [% c.user.username %], you have the following roles:</p>
7ce05098 114
d442cc9f 115 <ul>
116 [% # Dump list of roles -%]
2a6eb5f9 117 [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
d442cc9f 118 </ul>
7ce05098 119
d442cc9f 120 <p>
121 [% # Add some simple role-specific logic to template %]
122 [% # Use $c->check_user_roles() to check authz -%]
8a7c5151 123 [% IF c.check_user_roles('user') %]
d442cc9f 124 [% # Give normal users a link for 'logout' %]
e075db0c 125 <a href="[% c.uri_for('/logout') %]">User Logout</a>
d442cc9f 126 [% END %]
7ce05098 127
d442cc9f 128 [% # Can also use $c->user->check_roles() to check authz -%]
8a7c5151 129 [% IF c.check_user_roles('admin') %]
d442cc9f 130 [% # Give admin users a link for 'create' %]
0416017e 131 <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
d442cc9f 132 [% END %]
133 </p>
134
135This code displays a different combination of links depending on the
bf4d990b 136roles assigned to the user.
d442cc9f 137
1390ef0e 138
8a472b34 139=head2 Limit Books::add to 'admin' Users
d442cc9f 140
141C<IF> statements in TT templates simply control the output that is sent
142to the user's browser; it provides no real enforcement (if users know or
143guess the appropriate URLs, they are still perfectly free to hit any
144action within your application). We need to enhance the controller
145logic to wrap restricted actions with role-validation logic.
146
147For example, we might want to restrict the "formless create" action to
148admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
149updating C<url_create> to match the following code:
150
151 =head2 url_create
7ce05098 152
d442cc9f 153 Create a book with the supplied title and rating,
154 with manual authorization
7ce05098 155
d442cc9f 156 =cut
7ce05098 157
e075db0c 158 sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
d442cc9f 159 # In addition to self & context, get the title, rating & author_id args
160 # from the URL. Note that Catalyst automatically puts extra information
161 # after the "/<controller_name>/<action_name/" into @_
162 my ($self, $c, $title, $rating, $author_id) = @_;
7ce05098 163
d442cc9f 164 # Check the user's roles
165 if ($c->check_user_roles('admin')) {
905a3a26 166 # Call create() on the book model object. Pass the table
d442cc9f 167 # columns/field values we want to set as hash values
3b1fa91b 168 my $book = $c->model('DB::Book')->create({
d442cc9f 169 title => $title,
170 rating => $rating
171 });
7ce05098 172
905a3a26 173 # Add a record to the join table for this book, mapping to
d442cc9f 174 # appropriate author
2a6eb5f9 175 $book->add_to_book_authors({author_id => $author_id});
d442cc9f 176 # Note: Above is a shortcut for this:
2a6eb5f9 177 # $book->create_related('book_authors', {author_id => $author_id});
7ce05098 178
6c0f71ee 179 # Assign the Book object to the stash and set template
180 $c->stash(book => $book,
aa7ff325 181 template => 'books/create_done.tt2');
d442cc9f 182 } else {
e075db0c 183 # Provide very simple feedback to the user.
d442cc9f 184 $c->response->body('Unauthorized!');
185 }
186 }
187
188
189To add authorization, we simply wrap the main code of this method in an
190C<if> statement that calls C<check_user_roles>. If the user does not
191have the appropriate permissions, they receive an "Unauthorized!"
192message. Note that we intentionally chose to display the message this
193way to demonstrate that TT templates will not be used if the response
194body has already been set. In reality you would probably want to use a
195technique that maintains the visual continuity of your template layout
64775f18 196(for example, using L<Catalyst::Plugin::StatusMessage> as shown in the
333f9299 197L<last chapter|Catalyst::Manual::Tutorial::05_Authentication> to
198redirect to an "unauthorized" page).
d442cc9f 199
200B<TIP>: If you want to keep your existing C<url_create> method, you can
201create a new copy and comment out the original by making it look like a
bf4d990b 202Pod comment. For example, put something like C<=begin> before
e075db0c 203C<sub add : Local {> and C<=end> after the closing C<}>.
d442cc9f 204
1390ef0e 205
d442cc9f 206=head2 Try Out Authentication And Authorization
207
6c0f71ee 208Make sure the development server is running:
d442cc9f 209
6c0f71ee 210 $ script/myapp_server.pl -r
d442cc9f 211
bf4d990b 212Now trying going to L<http://localhost:3000/books/list> and you should
213be taken to the login page (you might have to C<Shift+Reload> or
214C<Ctrl+Reload> your browser and/or click the "User Logout" link on the
215book list page). Try logging in with both C<test01> and C<test02> (both
216use a password of C<mypass>) and notice how the roles information
217updates at the bottom of the "Book List" page. Also try the "User
218Logout" link on the book list page.
d442cc9f 219
220Now the "url_create" URL will work if you are already logged in as user
221C<test01>, but receive an authorization failure if you are logged in as
222C<test02>. Try:
223
224 http://localhost:3000/books/url_create/test/1/6
225
bf4d990b 226while logged in as each user. Use one of the "logout" links (or go to
227L<http://localhost:3000/logout> in your browser directly) when you are
d442cc9f 228done.
229
230
acbd7bdd 231=head1 ENABLE MODEL-BASED AUTHORIZATION
d442cc9f 232
bf4d990b 233Hopefully it's fairly obvious that adding detailed permission checking
234logic to our controllers and view templates isn't a very clean or
235scalable way to build role-based permissions into out application. As
236with many other aspects of MVC web development, the goal is to have your
237controllers and views be an "thin" as possible, with all of the "fancy
238business logic" built into your model.
d442cc9f 239
bf4d990b 240For example, let's add a method to our C<Books.pm> Result Class to check
241if a user is allowed to delete a book. Open
242C<lib/MyApp/Schema/Result/Book.pm> and add the following method (be sure
243to add it below the "C<DO NOT MODIFY ...>" line):
d442cc9f 244
acbd7bdd 245 =head2 delete_allowed_by
7ce05098 246
acbd7bdd 247 Can the specified user delete the current book?
7ce05098 248
acbd7bdd 249 =cut
7ce05098 250
acbd7bdd 251 sub delete_allowed_by {
252 my ($self, $user) = @_;
7ce05098 253
acbd7bdd 254 # Only allow delete if user has 'admin' role
255 return $user->has_role('admin');
256 }
d442cc9f 257
bf4d990b 258Here we call a C<has_role> method on our user object, so we should add
259this method to our Result Class. Open
260C<lib/MyApp/Schema/Result/User.pm> and add the following method below
8e571e49 261the "C<DO NOT MODIFY ...>" line:
d442cc9f 262
aa7ff325 263 =head2 has_role
7ce05098 264
acbd7bdd 265 Check if a user has the specified role
7ce05098 266
acbd7bdd 267 =cut
7ce05098 268
8e571e49 269 use Perl6::Junction qw/any/;
acbd7bdd 270 sub has_role {
271 my ($self, $role) = @_;
7ce05098 272
acbd7bdd 273 # Does this user posses the required role?
274 return any(map { $_->role } $self->roles) eq $role;
275 }
d442cc9f 276
56a12748 277Let's also add L<Perl6::Junction> to the requirements listed in
3a44dd3c 278Makefile.PL:
279
280 requires 'Perl6::Junction';
281
56a12748 282B<Note:> Feel free to use C<grep> in lieu of L<Perl6::Junction::any|Perl6::Junction/any()> if
283you prefer. Also, please don't let the use of the L<Perl6::Junction>
20e49994 284module above lead you to believe that Catalyst is somehow dependent on
285Perl 6... we are simply using that module for its
286L<easy-to-read|http://blogs.perl.org/users/marc_sebastian_jakobs/2009/11/my-favorite-module-of-the-month-perl6junction.html>
287C<any> function.
288
acbd7bdd 289Now we need to add some enforcement inside our controller. Open
290C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
291match the following code:
d442cc9f 292
acbd7bdd 293 =head2 delete
7ce05098 294
acbd7bdd 295 Delete a book
7ce05098 296
d442cc9f 297 =cut
7ce05098 298
acbd7bdd 299 sub delete :Chained('object') :PathPart('delete') :Args(0) {
d442cc9f 300 my ($self, $c) = @_;
7ce05098 301
acbd7bdd 302 # Check permissions
303 $c->detach('/error_noperms')
304 unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
7ce05098 305
959ce394 306 # Saved the PK id for status_msg below
307 my $id = $c->stash->{object}->id;
7ce05098 308
acbd7bdd 309 # Use the book object saved by 'object' and delete it along
310 # with related 'book_authors' entries
311 $c->stash->{object}->delete;
7ce05098 312
acbd7bdd 313 # Redirect the user back to the list page
333f9299 314 $c->response->redirect($c->uri_for($self->action_for('list'),
315 {mid => $c->set_status_msg("Deleted book $id")}));
2a6eb5f9 316 }
acbd7bdd 317
bf4d990b 318Here, we C<detach> to an error page if the user is lacking the
319appropriate permissions. For this to work, we need to make arrangements
320for the '/error_noperms' action to work. Open
acbd7bdd 321C<lib/MyApp/Controller/Root.pm> and add this method:
322
323 =head2 error_noperms
7ce05098 324
acbd7bdd 325 Permissions error screen
7ce05098 326
acbd7bdd 327 =cut
7ce05098 328
614b484c 329 sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
acbd7bdd 330 my ($self, $c) = @_;
7ce05098 331
6c0f71ee 332 $c->stash(template => 'error_noperms.tt2');
d442cc9f 333 }
334
acbd7bdd 335And also add the template file by putting the following text into
336C<root/src/error_noperms.tt2>:
337
338 <span class="error">Permission Denied</span>
339
acbd7bdd 340Log in as C<test01> and create several new books using the C<url_create>
341feature:
342
343 http://localhost:3000/books/url_create/Test/1/4
344
bf4d990b 345Then, while still logged in as C<test01>, click the "Delete" link next
346to one of these books. The book should be removed and you should see
347the usual green "Book deleted" message. Next, click the "User Logout"
348link and log back in as C<test02>. Now try deleting one of the books.
349You should be taken to the red "Permission Denied" message on our error
350page.
d442cc9f 351
4feda61c 352Use one of the 'Logout' links (or go to the
d442cc9f 353L<http://localhost:3000/logout> URL directly) when you are done.
354
355
24acc5d7 356You can jump to the next chapter of the tutorial here:
357L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
358
359
d442cc9f 360=head1 AUTHOR
361
362Kennedy Clark, C<hkclark@gmail.com>
363
53243324 364Feel free to contact the author for any errors or suggestions, but the
365best way to report issues is via the CPAN RT Bug system at
bb0999d3 366L<https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
53243324 367
bb0999d3 368Copyright 2006-2011, Kennedy Clark, under the
ec3ef4ad 369Creative Commons Attribution Share-Alike License Version 3.0
56a12748 370(L<https://creativecommons.org/licenses/by-sa/3.0/us/>).