Go from L<Module::Name|Module::Name> to L<Module::Name>
[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
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
2217b252 68L<Catalyst::Manual::Tutorial::01_Intro>.
1390ef0e 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
aa7ff325 81 # Load plugins
82 use Catalyst qw/
83 -Debug
84 ConfigLoader
85 Static::Simple
86
87 StackTrace
88
89 Authentication
90 Authorization::Roles
91
92 Session
95455c74 93 Session::Store::File
aa7ff325 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
104
105Open C<root/src/books/list.tt2> in your editor and add the following
106lines to the bottom of the file:
107
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>
130
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.
142
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:
146
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 }
183
184
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
202
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:
218
219 http://localhost:3000/books/url_create/test/1/6
220
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.
224
225
acbd7bdd 226=head1 ENABLE MODEL-BASED AUTHORIZATION
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
241
242 Can the specified user delete the current book?
243
244 =cut
245
246 sub delete_allowed_by {
247 my ($self, $user) = @_;
248
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
261
262 =cut
263
8e571e49 264 use Perl6::Junction qw/any/;
acbd7bdd 265 sub has_role {
266 my ($self, $role) = @_;
267
268 # Does this user posses the required role?
269 return any(map { $_->role } $self->roles) eq $role;
270 }
d442cc9f 271
3a44dd3c 272Let's also add Perl6::Junction to the requirements listed in
273Makefile.PL:
274
275 requires 'Perl6::Junction';
276
acbd7bdd 277Now we need to add some enforcement inside our controller. Open
278C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
279match the following code:
d442cc9f 280
acbd7bdd 281 =head2 delete
1390ef0e 282
acbd7bdd 283 Delete a book
1390ef0e 284
d442cc9f 285 =cut
1390ef0e 286
acbd7bdd 287 sub delete :Chained('object') :PathPart('delete') :Args(0) {
d442cc9f 288 my ($self, $c) = @_;
1390ef0e 289
acbd7bdd 290 # Check permissions
291 $c->detach('/error_noperms')
292 unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
293
294 # Use the book object saved by 'object' and delete it along
295 # with related 'book_authors' entries
296 $c->stash->{object}->delete;
1390ef0e 297
acbd7bdd 298 # Use 'flash' to save information across requests until it's read
299 $c->flash->{status_msg} = "Book deleted";
300
301 # Redirect the user back to the list page
302 $c->response->redirect($c->uri_for($self->action_for('list')));
2a6eb5f9 303 }
acbd7bdd 304
305Here, we C<detach> to an error page if the user is lacking the
306appropriate permissions. For this to work, we need to make
307arrangements for the '/error_noperms' action to work. Open
308C<lib/MyApp/Controller/Root.pm> and add this method:
309
310 =head2 error_noperms
311
312 Permissions error screen
313
314 =cut
315
614b484c 316 sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
acbd7bdd 317 my ($self, $c) = @_;
318
6c0f71ee 319 $c->stash(template => 'error_noperms.tt2');
d442cc9f 320 }
321
acbd7bdd 322And also add the template file by putting the following text into
323C<root/src/error_noperms.tt2>:
324
325 <span class="error">Permission Denied</span>
326
acbd7bdd 327Log in as C<test01> and create several new books using the C<url_create>
328feature:
329
330 http://localhost:3000/books/url_create/Test/1/4
331
332Then, while still logged in as C<test01>, click the "Delete" link next
333to one of these books. The book should be removed and you should see
334the usual green "Book deleted" message. Next, click the "User Logout"
335link and log back in as C<test02>. Now try deleting one of the books.
336You should be taken to the red "Permission Denied" message on our
337error page.
d442cc9f 338
4feda61c 339Use one of the 'Logout' links (or go to the
d442cc9f 340L<http://localhost:3000/logout> URL directly) when you are done.
341
342
343=head1 AUTHOR
344
345Kennedy Clark, C<hkclark@gmail.com>
346
53243324 347Feel free to contact the author for any errors or suggestions, but the
348best way to report issues is via the CPAN RT Bug system at
349<https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
350
351The most recent version of the Catalyst Tutorial can be found at
59884771 352L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
d442cc9f 353
ec3ef4ad 354Copyright 2006-2010, Kennedy Clark, under the
355Creative Commons Attribution Share-Alike License Version 3.0
95674086 356(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).