1Catalyst::Manual::TutorUisaelr::C0o6n_tArCuiatbthuaotlreyidsztaP:te:irMolann(Du3oa)clu:m:eTnuttaotriioanl::06_Authorization(3)
2
3
4

NAME

6       Catalyst::Manual::Tutorial::06_Authorization - Catalyst Tutorial -
7       Chapter 6: Authorization
8

OVERVIEW

10       This is Chapter 6 of 10 for the Catalyst tutorial.
11
12       Tutorial Overview
13
14       1.  Introduction
15
16       2.  Catalyst Basics
17
18       3.  More Catalyst Basics
19
20       4.  Basic CRUD
21
22       5.  Authentication
23
24       6.  06_Authorization
25
26       7.  Debugging
27
28       8.  Testing
29
30       9.  Advanced CRUD
31
32       10. Appendices
33

DESCRIPTION

35       This chapter of the tutorial adds role-based authorization to the
36       existing authentication implemented in Chapter 5.  It provides simple
37       examples of how to use roles in both TT templates and controller
38       actions.  The first half looks at basic authorization concepts. The
39       second half looks at how moving your authorization code to your model
40       can simplify your code and make things easier to maintain.
41
42       You can checkout the source code for this example from the catalyst
43       subversion repository as per the instructions in
44       Catalyst::Manual::Tutorial::01_Intro.
45

BASIC AUTHORIZATION

47       In this section you learn the basics of how authorization works under
48       Catalyst.
49
50   Update Plugins to Include Support for Authorization
51       Edit "lib/MyApp.pm" and add "Authorization::Roles" to the list:
52
53           # Load plugins
54           use Catalyst qw/
55               -Debug
56               ConfigLoader
57               Static::Simple
58
59               StackTrace
60
61               Authentication
62               Authorization::Roles
63
64               Session
65               Session::Store::FastMmap
66               Session::State::Cookie
67           /;
68
69       Once again, include this additional plugin as a new dependency in the
70       Makefile.PL file like this:
71
72           requires 'Catalyst::Plugin::Authorization::Roles';
73
74   Add Role-Specific Logic to the "Book List" Template
75       Open "root/src/books/list.tt2" in your editor and add the following
76       lines to the bottom of the file:
77
78           ...
79           <p>Hello [% c.user.username %], you have the following roles:</p>
80
81           <ul>
82             [% # Dump list of roles -%]
83             [% FOR role = c.user.roles %]<li>[% role %]</li>[% END %]
84           </ul>
85
86           <p>
87           [% # Add some simple role-specific logic to template %]
88           [% # Use $c->check_user_roles() to check authz -%]
89           [% IF c.check_user_roles('user') %]
90             [% # Give normal users a link for 'logout' %]
91             <a href="[% c.uri_for('/logout') %]">User Logout</a>
92           [% END %]
93
94           [% # Can also use $c->user->check_roles() to check authz -%]
95           [% IF c.check_user_roles('admin') %]
96             [% # Give admin users a link for 'create' %]
97             <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
98           [% END %]
99           </p>
100
101       This code displays a different combination of links depending on the
102       roles assigned to the user.
103
104   Limit Books::add to 'admin' Users
105       "IF" statements in TT templates simply control the output that is sent
106       to the user's browser; it provides no real enforcement (if users know
107       or guess the appropriate URLs, they are still perfectly free to hit any
108       action within your application).  We need to enhance the controller
109       logic to wrap restricted actions with role-validation logic.
110
111       For example, we might want to restrict the "formless create" action to
112       admin-level users by editing "lib/MyApp/Controller/Books.pm" and
113       updating "url_create" to match the following code:
114
115           =head2 url_create
116
117           Create a book with the supplied title and rating,
118           with manual authorization
119
120           =cut
121
122           sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
123               # In addition to self & context, get the title, rating & author_id args
124               # from the URL.  Note that Catalyst automatically puts extra information
125               # after the "/<controller_name>/<action_name/" into @_
126               my ($self, $c, $title, $rating, $author_id) = @_;
127
128               # Check the user's roles
129               if ($c->check_user_roles('admin')) {
130                   # Call create() on the book model object. Pass the table
131                   # columns/field values we want to set as hash values
132                   my $book = $c->model('DB::Book')->create({
133                           title   => $title,
134                           rating  => $rating
135                       });
136
137                   # Add a record to the join table for this book, mapping to
138                   # appropriate author
139                   $book->add_to_book_authors({author_id => $author_id});
140                   # Note: Above is a shortcut for this:
141                   # $book->create_related('book_authors', {author_id => $author_id});
142
143                   # Assign the Book object to the stash and set template
144                   $c->stash(book     => $book,
145                             template => 'books/create_done.tt2');
146               } else {
147                   # Provide very simple feedback to the user.
148                   $c->response->body('Unauthorized!');
149               }
150           }
151
152       To add authorization, we simply wrap the main code of this method in an
153       "if" statement that calls "check_user_roles".  If the user does not
154       have the appropriate permissions, they receive an "Unauthorized!"
155       message.  Note that we intentionally chose to display the message this
156       way to demonstrate that TT templates will not be used if the response
157       body has already been set.  In reality you would probably want to use a
158       technique that maintains the visual continuity of your template layout
159       (for example, using the "status" or "error" message feature added in
160       Chapter 3 or "detach" to an action that shows an "unauthorized" page).
161
162       TIP: If you want to keep your existing "url_create" method, you can
163       create a new copy and comment out the original by making it look like a
164       Pod comment.  For example, put something like "=begin" before "sub add
165       : Local {" and "=end" after the closing "}".
166
167   Try Out Authentication And Authorization
168       Make sure the development server is running:
169
170           $ script/myapp_server.pl -r
171
172       Now trying going to <http://localhost:3000/books/list> and you should
173       be taken to the login page (you might have to "Shift+Reload" or
174       "Ctrl+Reload" your browser and/or click the "User Logout" link on the
175       book list page).  Try logging in with both "test01" and "test02" (both
176       use a password of "mypass") and notice how the roles information
177       updates at the bottom of the "Book List" page. Also try the "User
178       Logout" link on the book list page.
179
180       Now the "url_create" URL will work if you are already logged in as user
181       "test01", but receive an authorization failure if you are logged in as
182       "test02".  Try:
183
184           http://localhost:3000/books/url_create/test/1/6
185
186       while logged in as each user.  Use one of the "logout" links (or go to
187       <http://localhost:3000/logout> in your browser directly) when you are
188       done.
189

ENABLE MODEL-BASED AUTHORIZATION

191       Hopefully it's fairly obvious that adding detailed permission checking
192       logic to our controllers and view templates isn't a very clean or
193       scalable way to build role-based permissions into out application.  As
194       with many other aspects of MVC web development, the goal is to have
195       your controllers and views be an "thin" as possible, with all of the
196       "fancy business logic" built into your model.
197
198       For example, let's add a method to our "Books.pm" Result Class to check
199       if a user is allowed to delete a book.  Open
200       "lib/MyApp/Schema/Result/Book.pm" and add the following method (be sure
201       to add it below the ""DO NOT MODIFY ..."" line):
202
203           =head2 delete_allowed_by
204
205           Can the specified user delete the current book?
206
207           =cut
208
209           sub delete_allowed_by {
210               my ($self, $user) = @_;
211
212               # Only allow delete if user has 'admin' role
213               return $user->has_role('admin');
214           }
215
216       Here we call a "has_role" method on our user object, so we should add
217       this method to our Result Class.  Open
218       "lib/MyApp/Schema/Result/User.pm" and add the following method below
219       the ""DO NOT MODIFY ..."" line:
220
221           =head2 has_role
222
223           Check if a user has the specified role
224
225           =cut
226
227           use Perl6::Junction qw/any/;
228           sub has_role {
229               my ($self, $role) = @_;
230
231               # Does this user posses the required role?
232               return any(map { $_->role } $self->roles) eq $role;
233           }
234
235       Now we need to add some enforcement inside our controller.  Open
236       "lib/MyApp/Controller/Books.pm" and update the "delete" method to match
237       the following code:
238
239           =head2 delete
240
241           Delete a book
242
243           =cut
244
245           sub delete :Chained('object') :PathPart('delete') :Args(0) {
246               my ($self, $c) = @_;
247
248               # Check permissions
249               $c->detach('/error_noperms')
250                   unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
251
252               # Use the book object saved by 'object' and delete it along
253               # with related 'book_authors' entries
254               $c->stash->{object}->delete;
255
256               # Use 'flash' to save information across requests until it's read
257               $c->flash->{status_msg} = "Book deleted";
258
259               # Redirect the user back to the list page
260               $c->response->redirect($c->uri_for($self->action_for('list')));
261           }
262
263       Here, we "detach" to an error page if the user is lacking the
264       appropriate permissions.  For this to work, we need to make
265       arrangements for the '/error_noperms' action to work.  Open
266       "lib/MyApp/Controller/Root.pm" and add this method:
267
268           =head2 error_noperms
269
270           Permissions error screen
271
272           =cut
273
274           sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
275               my ($self, $c) = @_;
276
277               $c->stash(template => 'error_noperms.tt2');
278           }
279
280       And also add the template file by putting the following text into
281       "root/src/error_noperms.tt2":
282
283           <span class="error">Permission Denied</span>
284
285       Log in as "test01" and create several new books using the "url_create"
286       feature:
287
288           http://localhost:3000/books/url_create/Test/1/4
289
290       Then, while still logged in as "test01", click the "Delete" link next
291       to one of these books.  The book should be removed and you should see
292       the usual green "Book deleted" message.  Next, click the "User Logout"
293       link and log back in as "test02".  Now try deleting one of the books.
294       You should be taken to the red "Permission Denied" message on our error
295       page.
296
297       Use one of the 'Logout' links (or go to the
298       <http://localhost:3000/logout> URL directly) when you are done.
299

AUTHOR

301       Kennedy Clark, "hkclark@gmail.com"
302
303       Please report any errors, issues or suggestions to the author.  The
304       most recent version of the Catalyst Tutorial can be found at
305       http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/
306       <http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-
307       Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
308
309       Copyright 2006-2008, Kennedy Clark, under Creative Commons License
310       (http://creativecommons.org/licenses/by-sa/3.0/us/
311       <http://creativecommons.org/licenses/by-sa/3.0/us/>).
312
313
314
315perl v5.12.0                   Cat2a0l1y0s-t0:2:-M1a7nual::Tutorial::06_Authorization(3)
Impressum