Remove todo
[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
b1b6582a 67Source code for the tutorial in included in the F</root/Final> directory
68of the Tutorial Virtual machine (one subdirectory per chapter). There
69are 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
bf4d990b 88
aa7ff325 89 StackTrace
bf4d990b 90
aa7ff325 91 Authentication
92 Authorization::Roles
bf4d990b 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>
1390ef0e 114
d442cc9f 115 <ul>
116 [% # Dump list of roles -%]
2a6eb5f9 117 [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
d442cc9f 118 </ul>
1390ef0e 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 %]
1390ef0e 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
1390ef0e 152
d442cc9f 153 Create a book with the supplied title and rating,
154 with manual authorization
1390ef0e 155
d442cc9f 156 =cut
1390ef0e 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) = @_;
1390ef0e 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 });
1390ef0e 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});
1390ef0e 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
333f9299 196(for example, using L<Catalyst::Plugin::StateMessage> as shown in the
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
246
247 Can the specified user delete the current book?
248
249 =cut
250
251 sub delete_allowed_by {
252 my ($self, $user) = @_;
bf4d990b 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
acbd7bdd 264
265 Check if a user has the specified role
266
267 =cut
268
8e571e49 269 use Perl6::Junction qw/any/;
acbd7bdd 270 sub has_role {
271 my ($self, $role) = @_;
272
273 # Does this user posses the required role?
274 return any(map { $_->role } $self->roles) eq $role;
275 }
d442cc9f 276
20e49994 277Let's also add C<Perl6::Junction> to the requirements listed in
3a44dd3c 278Makefile.PL:
279
280 requires 'Perl6::Junction';
281
20e49994 282B<Note:> Feel free to use C<grep> in lieu of C<Perl6::Junction::any> if
283you prefer. Also, please don't let the use of the C<Perl6::Junction>
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
1390ef0e 294
acbd7bdd 295 Delete a book
1390ef0e 296
d442cc9f 297 =cut
1390ef0e 298
acbd7bdd 299 sub delete :Chained('object') :PathPart('delete') :Args(0) {
d442cc9f 300 my ($self, $c) = @_;
1390ef0e 301
acbd7bdd 302 # Check permissions
303 $c->detach('/error_noperms')
304 unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
305
306 # Use the book object saved by 'object' and delete it along
307 # with related 'book_authors' entries
308 $c->stash->{object}->delete;
1390ef0e 309
acbd7bdd 310 # Redirect the user back to the list page
333f9299 311 $c->response->redirect($c->uri_for($self->action_for('list'),
312 {mid => $c->set_status_msg("Deleted book $id")}));
2a6eb5f9 313 }
acbd7bdd 314
bf4d990b 315Here, we C<detach> to an error page if the user is lacking the
316appropriate permissions. For this to work, we need to make arrangements
317for the '/error_noperms' action to work. Open
acbd7bdd 318C<lib/MyApp/Controller/Root.pm> and add this method:
319
320 =head2 error_noperms
321
322 Permissions error screen
323
324 =cut
bf4d990b 325
614b484c 326 sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
acbd7bdd 327 my ($self, $c) = @_;
328
6c0f71ee 329 $c->stash(template => 'error_noperms.tt2');
d442cc9f 330 }
331
acbd7bdd 332And also add the template file by putting the following text into
333C<root/src/error_noperms.tt2>:
334
335 <span class="error">Permission Denied</span>
336
acbd7bdd 337Log in as C<test01> and create several new books using the C<url_create>
338feature:
339
340 http://localhost:3000/books/url_create/Test/1/4
341
bf4d990b 342Then, while still logged in as C<test01>, click the "Delete" link next
343to one of these books. The book should be removed and you should see
344the usual green "Book deleted" message. Next, click the "User Logout"
345link and log back in as C<test02>. Now try deleting one of the books.
346You should be taken to the red "Permission Denied" message on our error
347page.
d442cc9f 348
4feda61c 349Use one of the 'Logout' links (or go to the
d442cc9f 350L<http://localhost:3000/logout> URL directly) when you are done.
351
352
353=head1 AUTHOR
354
355Kennedy Clark, C<hkclark@gmail.com>
356
53243324 357Feel free to contact the author for any errors or suggestions, but the
358best way to report issues is via the CPAN RT Bug system at
359<https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
360
361The most recent version of the Catalyst Tutorial can be found at
59884771 362L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
d442cc9f 363
ec3ef4ad 364Copyright 2006-2010, Kennedy Clark, under the
365Creative Commons Attribution Share-Alike License Version 3.0
95674086 366(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).