=head1 DESCRIPTION
-This chapter of the tutorial adds role-based authorization to the
-existing authentication implemented in Chapter 5. It provides simple
-examples of how to use roles in both TT templates and controller
-actions. The first half looks at basic authorization concepts. The
-second half looks at how moving your authorization code to your model
+This chapter of the tutorial adds role-based authorization to the
+existing authentication implemented in
+L<Chapter 5|Catalyst::Manual::Tutorial::05_Authentication>. It provides
+simple examples of how to use roles in both TT templates and controller
+actions. The first half looks at basic authorization concepts. The
+second half looks at how moving your authorization code to your model
can simplify your code and make things easier to maintain.
-You can checkout the source code for this example from the catalyst
-subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+Source code for the tutorial in included in the F</home/catalyst/Final>
+directory of the Tutorial Virtual machine (one subdirectory per
+chapter). There are also instructions for downloading the code in
+L<Catalyst::Manual::Tutorial::01_Intro>.
=head1 BASIC AUTHORIZATION
-In this section you learn the basics of how authorization works under
+In this section you learn the basics of how authorization works under
Catalyst.
# Load plugins
use Catalyst qw/
- -Debug
- ConfigLoader
- Static::Simple
-
- StackTrace
-
- Authentication
- Authorization::Roles
-
- Session
- Session::Store::FastMmap
- Session::State::Cookie
- /;
-
-B<Note:> As discussed in MoreCatalystBasics, different versions of
-C<Catalyst::Devel> have used a variety of methods to load the plugins.
-You can put the plugins in the C<use Catalyst> statement if you
-prefer.
-
-Once again (remain sharp, by now you should be getting the hang of things)
-include this additional plugin as a new dependency in the Makefile.PL file
-like this:
-
- requires (
- ...
- 'Catalyst::Plugin::Authorization::Roles' => '0',
- );
+ -Debug
+ ConfigLoader
+ Static::Simple
+
+ StackTrace
+
+ Authentication
+ Authorization::Roles
+
+ Session
+ Session::Store::File
+ Session::State::Cookie
+
+ StatusMessage
+ /;
+
+Once again, include this additional plugin as a new dependency in the
+Makefile.PL file like this:
+
+ requires 'Catalyst::Plugin::Authorization::Roles';
=head2 Add Role-Specific Logic to the "Book List" Template
# Note: Above is a shortcut for this:
# $book->create_related('book_authors', {author_id => $author_id});
- # Assign the Book object to the stash for display in the view
- $c->stash->{book} = $book;
-
- # Set the TT template to use
- $c->stash->{template} = 'books/create_done.tt2';
+ # Assign the Book object to the stash and set template
+ $c->stash(book => $book,
+ template => 'books/create_done.tt2');
} else {
# Provide very simple feedback to the user.
$c->response->body('Unauthorized!');
way to demonstrate that TT templates will not be used if the response
body has already been set. In reality you would probably want to use a
technique that maintains the visual continuity of your template layout
-(for example, using the "status" or "error" message feature added in
-Chapter 3 or C<detach> to an action that shows an "unauthorized" page).
+(for example, using L<Catalyst::Plugin::StatusMessage> as shown in the
+L<last chapter|Catalyst::Manual::Tutorial::05_Authentication> to
+redirect to an "unauthorized" page).
B<TIP>: If you want to keep your existing C<url_create> method, you can
create a new copy and comment out the original by making it look like a
-Pod comment. For example, put something like C<=begin> before
+Pod comment. For example, put something like C<=begin> before
C<sub add : Local {> and C<=end> after the closing C<}>.
=head2 Try Out Authentication And Authorization
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
+Make sure the development server is running:
- $ script/myapp_server.pl
+ $ script/myapp_server.pl -r
-Now trying going to L<http://localhost:3000/books/list> and you should
-be taken to the login page (you might have to C<Shift+Reload> or
-C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book
-list page). Try logging in with both C<test01> and C<test02> (both
-use a password of C<mypass>) and notice how the roles information
-updates at the bottom of the "Book List" page. Also try the "User Logout"
-link on the book list page.
+Now trying going to L<http://localhost:3000/books/list> and you should
+be taken to the login page (you might have to C<Shift+Reload> or
+C<Ctrl+Reload> your browser and/or click the "User Logout" link on the
+book list page). Try logging in with both C<test01> and C<test02> (both
+use a password of C<mypass>) and notice how the roles information
+updates at the bottom of the "Book List" page. Also try the "User
+Logout" link on the book list page.
Now the "url_create" URL will work if you are already logged in as user
C<test01>, but receive an authorization failure if you are logged in as
http://localhost:3000/books/url_create/test/1/6
-while logged in as each user. Use one of the "logout" links (or go to
-L<http://localhost:3000/logout> in your browser directly) when you are
+while logged in as each user. Use one of the "logout" links (or go to
+L<http://localhost:3000/logout> in your browser directly) when you are
done.
=head1 ENABLE MODEL-BASED AUTHORIZATION
-Hopefully it's fairly obvious that adding detailed permission checking
-logic to our controllers and view templates isn't a very clean or
-scalable way to build role-based permissions into out application. As
-with many other aspects of MVC web development, the goal is to have
-your controllers and views be an "thin" as possible, with all of the
-"fancy business logic" built into your model.
+Hopefully it's fairly obvious that adding detailed permission checking
+logic to our controllers and view templates isn't a very clean or
+scalable way to build role-based permissions into out application. As
+with many other aspects of MVC web development, the goal is to have your
+controllers and views be an "thin" as possible, with all of the "fancy
+business logic" built into your model.
-For example, let's add a method to our C<Books.pm> Result Class to
-check if a user is allowed to delete a book. Open
-C<lib/MyApp/Schema/Result/Book.pm> and add the following method
-(be sure to add it below the "C<DO NOT MODIFY ...>" line):
+For example, let's add a method to our C<Books.pm> Result Class to check
+if a user is allowed to delete a book. Open
+C<lib/MyApp/Schema/Result/Book.pm> and add the following method (be sure
+to add it below the "C<DO NOT MODIFY ...>" line):
=head2 delete_allowed_by
sub delete_allowed_by {
my ($self, $user) = @_;
-
+
# Only allow delete if user has 'admin' role
return $user->has_role('admin');
}
-Here we call a C<has_role> method on our user object, so we should add
-this method to our Result Class. Open
-C<lib/MyApp/Schema/Result/User.pm> and add the following method below
+Here we call a C<has_role> method on our user object, so we should add
+this method to our Result Class. Open
+C<lib/MyApp/Schema/Result/User.pm> and add the following method below
the "C<DO NOT MODIFY ...>" line:
- =head 2 has_role
+ =head2 has_role
Check if a user has the specified role
return any(map { $_->role } $self->roles) eq $role;
}
+Let's also add C<Perl6::Junction> to the requirements listed in
+Makefile.PL:
+
+ requires 'Perl6::Junction';
+
+B<Note:> Feel free to use C<grep> in lieu of C<Perl6::Junction::any> if
+you prefer. Also, please don't let the use of the C<Perl6::Junction>
+module above lead you to believe that Catalyst is somehow dependent on
+Perl 6... we are simply using that module for its
+L<easy-to-read|http://blogs.perl.org/users/marc_sebastian_jakobs/2009/11/my-favorite-module-of-the-month-perl6junction.html>
+C<any> function.
+
Now we need to add some enforcement inside our controller. Open
C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
match the following code:
$c->detach('/error_noperms')
unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
+ # Saved the PK id for status_msg below
+ my $id = $c->stash->{object}->id;
+
# Use the book object saved by 'object' and delete it along
# with related 'book_authors' entries
$c->stash->{object}->delete;
- # Use 'flash' to save information across requests until it's read
- $c->flash->{status_msg} = "Book deleted";
-
# Redirect the user back to the list page
- $c->response->redirect($c->uri_for($self->action_for('list')));
+ $c->response->redirect($c->uri_for($self->action_for('list'),
+ {mid => $c->set_status_msg("Deleted book $id")}));
}
-Here, we C<detach> to an error page if the user is lacking the
-appropriate permissions. For this to work, we need to make
-arrangements for the '/error_noperms' action to work. Open
+Here, we C<detach> to an error page if the user is lacking the
+appropriate permissions. For this to work, we need to make arrangements
+for the '/error_noperms' action to work. Open
C<lib/MyApp/Controller/Root.pm> and add this method:
=head2 error_noperms
Permissions error screen
=cut
-
- sub error_noperms :Chained('/') :PathPath('error_noperms') :Args(0) {
+
+ sub error_noperms :Chained('/') :PathPart('error_noperms') :Args(0) {
my ($self, $c) = @_;
- $c->stash->{template} = 'error_noperms.tt2';
+ $c->stash(template => 'error_noperms.tt2');
}
And also add the template file by putting the following text into
<span class="error">Permission Denied</span>
-Then run the Catalyst development server script:
-
- $ script/myapp_server.pl
-
Log in as C<test01> and create several new books using the C<url_create>
feature:
http://localhost:3000/books/url_create/Test/1/4
-Then, while still logged in as C<test01>, click the "Delete" link next
-to one of these books. The book should be removed and you should see
-the usual green "Book deleted" message. Next, click the "User Logout"
-link and log back in as C<test02>. Now try deleting one of the books.
-You should be taken to the red "Permission Denied" message on our
-error page.
+Then, while still logged in as C<test01>, click the "Delete" link next
+to one of these books. The book should be removed and you should see
+the usual green "Book deleted" message. Next, click the "User Logout"
+link and log back in as C<test02>. Now try deleting one of the books.
+You should be taken to the red "Permission Denied" message on our error
+page.
Use one of the 'Logout' links (or go to the
L<http://localhost:3000/logout> URL directly) when you are done.
+You can jump to the next chapter of the tutorial here:
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+
=head1 AUTHOR
Kennedy Clark, C<hkclark@gmail.com>
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+Feel free to contact the author for any errors or suggestions, but the
+best way to report issues is via the CPAN RT Bug system at
+L<https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+Copyright 2006-2011, Kennedy Clark, under the
+Creative Commons Attribution Share-Alike License Version 3.0
(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
-