--- /dev/null
+=head1 NAME
+
+Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Part 5: Authorization
+
+
+=head1 OVERVIEW
+
+This is B<Part 5 of 9> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
+
+=item 3
+
+L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+
+=item 4
+
+L<Authentication|Catalyst::Manual::Tutorial::Authentication>
+
+=item 5
+
+B<Authorization>
+
+=item 6
+
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+
+=item 7
+
+L<Testing|Catalyst::Manual::Tutorial::Testing>
+
+=item 8
+
+L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 9
+
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
+
+=back
+
+
+
+=head1 DESCRIPTION
+
+This part of the tutorial adds role-based authorization to the existing
+authentication implemented in Part 4. It provides simple examples of
+how to use roles in both TT templates and controller actions. The first
+half looks at manually configured authorization. The second half looks
+at how the ACL authorization plugin can simplify your code.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::Intro>
+
+=head1 BASIC AUTHORIZATION
+
+In this section you learn how to manually configure authorization.
+
+=head2 Update Plugins to Include Support for Authorization
+
+Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
+
+ use Catalyst qw/
+ -Debug
+ ConfigLoader
+ Static::Simple
+
+ StackTrace
+
+ Authentication
+ Authentication::Store::DBIC
+ Authentication::Credential::Password
+ Authorization::Roles
+
+ Session
+ Session::Store::FastMmap
+ Session::State::Cookie
+ /;
+
+
+=head2 Add Config Information for Authorization
+
+Edit C<myapp.yml> and update it to match (everything from the
+"authorization:" line down is new):
+
+ ---
+ name: MyApp
+ authentication:
+ dbic:
+ # Note this first definition would be the same as setting
+ # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User'
+ # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file).
+ #
+ # This is the model object created by Catalyst::Model::DBIC from your
+ # schema (you created 'MyAppDB::User' but as the Catalyst startup
+ # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User').
+ # NOTE: Omit 'MyApp::Model' here just as you would when using
+ # '$c->model("MyAppDB::User)'
+ user_class: MyAppDB::User
+ # This is the name of the field in your 'users' table that contains the user's name
+ user_field: username
+ # This is the name of the field in your 'users' table that contains the password
+ password_field: password
+ # Other options can go here for hashed passwords
+ # Enabled hashed passwords
+ password_type: hashed
+ # Use the SHA-1 hashing algorithm
+ password_hash_type: SHA-1
+ authorization:
+ dbic:
+ # This is the model object created by Catalyst::Model::DBIC from your
+ # schema (you created 'MyAppDB::Role' but as the Catalyst startup
+ # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::Role').
+ # NOTE: Omit 'MyApp::Model' here just as you would when using
+ # '$c->model("MyAppDB::User)'
+ role_class: MyAppDB::Role
+ # The name of the field in the 'roles' table that contains the role name
+ role_field: role
+ # The name of the accessor used to map a role to the users who have this role
+ # See the has_many() in MyAppDB/Role.pm
+ role_rel: map_user_role
+ # The name of the field in the user_role table that references the user
+ user_role_user_field: user_id
+
+
+=head2 Add Role-Specific Logic to the "Book List" Template
+
+Open C<root/src/books/list.tt2> in your editor and add the following
+lines to the bottom of the file:
+
+ <p>Hello [% Catalyst.user.username %], you have the following roles:</p>
+
+ <ul>
+ [% # Dump list of roles -%]
+ [% FOR role = Catalyst.user.roles %]<li>[% role %]</li>[% END %]
+ </ul>
+
+ <p>
+ [% # Add some simple role-specific logic to template %]
+ [% # Use $c->check_user_roles() to check authz -%]
+ [% IF Catalyst.check_user_roles('user') %]
+ [% # Give normal users a link for 'logout' %]
+ <a href="[% Catalyst.uri_for('/logout') %]">Logout</a>
+ [% END %]
+
+ [% # Can also use $c->user->check_roles() to check authz -%]
+ [% IF Catalyst.check_user_roles('admin') %]
+ [% # Give admin users a link for 'create' %]
+ <a href="[% Catalyst.uri_for('form_create') %]">Create</a>
+ [% END %]
+ </p>
+
+This code displays a different combination of links depending on the
+roles assigned to the user.
+
+=head2 Limit C<Books::add> to C<admin> Users
+
+C<IF> statements in TT templates simply control the output that is sent
+to the user's browser; it provides no real enforcement (if users know or
+guess the appropriate URLs, they are still perfectly free to hit any
+action within your application). We need to enhance the controller
+logic to wrap restricted actions with role-validation logic.
+
+For example, we might want to restrict the "formless create" action to
+admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
+updating C<url_create> to match the following code:
+
+ =head2 url_create
+
+ Create a book with the supplied title and rating,
+ with manual authorization
+
+ =cut
+
+ sub url_create : Local {
+ # In addition to self & context, get the title, rating & author_id args
+ # from the URL. Note that Catalyst automatically puts extra information
+ # after the "/<controller_name>/<action_name/" into @_
+ my ($self, $c, $title, $rating, $author_id) = @_;
+
+ # Check the user's roles
+ if ($c->check_user_roles('admin')) {
+ # Call create() on the book model object. Pass the table
+ # columns/field values we want to set as hash values
+ my $book = $c->model('MyAppDB::Book')->create({
+ title => $title,
+ rating => $rating
+ });
+
+ # Add a record to the join table for this book, mapping to
+ # appropriate author
+ $book->add_to_book_authors({author_id => $author_id});
+ # 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;
+
+ # This is a hack to disable XSUB processing in Data::Dumper
+ # (it's used in the view). This is a work-around for a bug in
+ # the interaction of some versions or Perl, Data::Dumper & DBIC.
+ # You won't need this if you aren't using Data::Dumper (or if
+ # you are running DBIC 0.06001 or greater), but adding it doesn't
+ # hurt anything either.
+ $Data::Dumper::Useperl = 1;
+
+ # Set the TT template to use
+ $c->stash->{template} = 'books/create_done.tt2';
+ } else {
+ # Provide very simple feedback to the user
+ $c->response->body('Unauthorized!');
+ }
+ }
+
+
+To add authorization, we simply wrap the main code of this method in an
+C<if> statement that calls C<check_user_roles>. If the user does not
+have the appropriate permissions, they receive an "Unauthorized!"
+message. Note that we intentionally chose to display the message this
+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
+Part 2).
+
+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 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:
+
+ $ script/myapp_server.pl
+
+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> your
+browser and/or click the "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 C<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
+C<test02>. Try:
+
+ 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 you browser directly) when you are
+done.
+
+
+=head1 ENABLE ACL-BASED AUTHORIZATION
+
+This section takes a brief look at how the
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
+plugin can automate much of the work required to perform role-based
+authorization in a Catalyst application.
+
+=head2 Add the C<Catalyst::Plugin::Authorization::ACL> Plugin
+
+Open C<lib/MyApp.pm> in your editor and add the following plugin to the
+C<use Catalyst> statement:
+
+ Authorization::ACL
+
+Note that the remaining C<use Catalyst> plugins from earlier sections
+are not shown here, but they should still be included.
+
+=head2 Add ACL Rules to the Application Class
+
+Open C<lib/MyApp.pm> in your editor and add the following B<BELOW> the
+C<__PACKAGE__-E<gt>setup;> statement:
+
+ # Authorization::ACL Rules
+ __PACKAGE__->deny_access_unless(
+ "/books/form_create",
+ [qw/admin/],
+ );
+ __PACKAGE__->deny_access_unless(
+ "/books/form_create_do",
+ [qw/admin/],
+ );
+ __PACKAGE__->deny_access_unless(
+ "/books/delete",
+ [qw/user admin/],
+ );
+
+Each of the three statements above comprises an ACL plugin "rule". The
+first two rules only allow admin-level users to create new books using
+the form (both the form itself and the data submission logic are
+protected). The third statement allows both users and admins to delete
+books. The C</books/url_create> action will continue to be protected by
+the "manually configured" authorization created earlier in this part of
+the tutorial.
+
+The ACL plugin permits you to apply allow/deny logic in a variety of
+ways. The following provides a basic overview of the capabilities:
+
+=over 4
+
+=item *
+
+The ACL plugin only operates on the Catalyst "private namespace". You
+are using the private namespace when you use C<Local> actions. C<Path>,
+C<Regex>, and C<Global> allow you to specify actions where the path and
+the namespace differ -- the ACL plugin will not work in these cases.
+
+=item *
+
+Each rule is expressed in a separate
+C<__PACKAGE__-E<gt>deny_access_unless()> or
+C<__PACKAGE__-E<gt>allow_access_if()> line (there are several other
+methods that can be used for more complex policies, see the C<METHODS>
+portion of the
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
+documentation for more details).
+
+=item *
+
+Each rule can contain multiple roles but only a single path.
+
+=item *
+
+The rules are tried in order (with the "most specific" rules tested
+first), and processing stops at the first "match" where an allow or deny
+is specified. Rules "fall through" if there is not a "match" (where a
+"match" means the user has the specified role). If a "match" is found,
+then processing stops there and the appropriate allow/deny action is
+taken.
+
+=item *
+
+If none of the rules match, then access is allowed.
+
+=item *
+
+The rules currently need to be specific in the application class
+C<lib\MyApp.pm> B<after> the C<__PACKAGE__-E<gt>setup;> line.
+
+=back
+
+=head2 Add a Method to Handle Access Violations
+
+By default,
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>
+throws an exception when authorization fails. This will take the user
+to the Catalyst debug screen, or a "Please come back later" message if
+you are not using the C<-Debug> flag. This step uses the
+C<access_denied> method in order to provide more appropriate feedback to
+the user.
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+ =head2 access_denied
+
+ Handle Catalyst::Plugin::Authorization::ACL access denied exceptions
+
+ =cut
+
+ sub access_denied : Private {
+ my ($self, $c) = @_;
+
+ # Set the error message
+ $c->stash->{error_msg} = 'Unauthorized!';
+
+ # Display the list
+ $c->forward('list');
+ }
+
+Then run the Catalyst development server script:
+
+ $ script/myapp_server.pl
+
+Log in as C<test02>. Once at the book list, click the "Create" link to
+try the C<form_create> action. You should receive a red "Unauthorized!"
+error message at the top of the list. (Note that in reality you would
+probably want to place the "Create" link code in
+C<root/src/books/list.tt2> inside an C<IF> statement that only displays
+the list to admin-level users.) If you log in as C<test01> you should
+be able to view the C<form_create> form and add a new book.
+
+When you are done, use one of the 'Logout' links (or go to the
+L<http://localhost:3000/logout> URL directly) when you are done.
+
+
+=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/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+