1Catalyst::Manual::TutorUisaelr::C0o6n_tArCuiatbthuaotlreyidsztaP:te:irMolann(Du3oa)clu:m:eTnuttaotriioanl::06_Authorization(3)
2
3
4
6 Catalyst::Manual::Tutorial::06_Authorization - Catalyst Tutorial -
7 Chapter 6: Authorization
8
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
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
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
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
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)