=item 3
-L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
=item 4
=item 9
-L<Appendices|Catalyst::Manual::Tutorial::Appendicies>
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
=back
This part of the tutorial is divided into two main sections: 1) basic,
cleartext authentication and 2) hash-based authentication.
-B<TIP>: Note that all of the code for this part of the tutorial can be
-pulled from the Catalyst Subversion repository in one step with the
-following command:
-
- svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@###
- IMPORTANT: Does not work yet. Will be completed for final version.
-
+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 AUTHENTICATION
=head2 Include Authentication and Session Plugins
-Edit C<lib/MyApp.pm> and update it as follows (everything below C<DefaultEnd> is new):
+Edit C<lib/MyApp.pm> and update it as follows (everything below C<StackTrace> is new):
use Catalyst qw/
-Debug
Static::Simple
StackTrace
- DefaultEnd
Authentication
Authentication::Store::DBIC
actions. Remember, Catalyst is designed to be very flexible, and leaves
such matters up to you, the designer and programmer.
-Then open C<lib/MyApp/Controller/Login.pm> and add:
+Then open C<lib/MyApp/Controller/Login.pm>, locate the C<sub index :
+Private> method (this was automatically inserted by the helpers when we
+created the Login controller above), and delete this line:
+
+ $c->response->body('Matched MyApp::Controller::Login in Login.');
+
+Then update it to match:
- =head2 base
+ =head2 index
Login logic
=cut
- sub base :Path :Args(0) {
+ sub index : Private {
my ($self, $c) = @_;
# Get the username and password from form
C<password> values are not present in the form, the user will be taken
to the empty login form.
-We are using C<sub base :Path :Args(0) {...}> here to specifically match
-the URL C</login>. C<Path> actions (aka, "literal actions") create URI
-matches relative to the namespace of the controller where they are defined.
-Although C<Path> supports arguments that allow relative and absolute paths
-to be defined, here we use an empty C<Path> definition to match on just the
-name of the controller itself. The method name, C<base>, is arbitrary.
-We make the match even more specific with the C<:Args(0)> action modifier
--- this forces the match on I<only> C</login>, not C</login/somethingelse>.
-
Note that we could have used something like C<sub default :Private>;
however, the use of C<default> actions is discouraged because it does
not receive path args as with other actions. The recommended practice
is to only use C<default> in C<MyApp::Controller::Root>.
-Next, create a corresponding method in C<lib/MyApp/Controller/Logout.pm>:
+Another option would be to use something like
+C<sub base :Path :Args(0) {...}> (where the C<...> refers to the login
+code shown in C<sub index : Private> above). We are using C<sub base
+:Path :Args(0) {...}> here to specifically match the URL C</login>.
+C<Path> actions (aka, "literal actions") create URI matches relative to
+the namespace of the controller where they are defined. Although
+C<Path> supports arguments that allow relative and absolute paths to be
+defined, here we use an empty C<Path> definition to match on just the
+name of the controller itself. The method name, C<base>, is arbitrary.
+We make the match even more specific with the C<:Args(0)> action
+modifier -- this forces the match on I<only> C</login>, not
+C</login/somethingelse>.
+
+Next, update the corresponding method in C<lib/MyApp/Controller/Logout.pm>
+to match:
- =head2 base
+ =head2 index
Logout logic
=cut
- sub base :Path :Args(0) {
+ sub index : Private {
my ($self, $c) = @_;
# Clear the user's state
$c->response->redirect($c->uri_for('/'));
}
-Note that we are using the same C<sub base :Path :Args(0) {...}> style
-of action as with the login logic.
+As with the login controller, be sure to delete the
+C<$c->response->body('Matched MyApp::Controller::Logout in Logout.');>
+line of the C<sub index>.
=head2 Add a Login Form TT Template Page
# Note that 'auto' runs after 'begin' but before your actions and that
# 'auto' "chain" (all from application path to most specific class are run)
+ # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
sub auto : Private {
my ($self, $c) = @_;
- # Allow unauthenticated users to reach the login page
- if ($c->request->path =~ /login/) {
+ # Allow unauthenticated users to reach the login page. This
+ # allows anauthenticated users to reach any action in the Login
+ # controller. To lock it down to a single action, we could use:
+ # if ($c->action eq $c->controller('Login')->action_for('index'))
+ # to only allow unauthenticated access to the C<index> action we
+ # added above.
+ if ($c->controller eq $c->controller('Login')) {
return 1;
}
=item *
+With C<begin>, C<end>, C<default>, C<index> private actions, only the
+most specific action of each type will be called. For example, if you
+define a C<begin> action in your controller it will I<override> a
+C<begin> action in your application/root controller -- I<only> the
+action in your controller will be called.
+
+=item *
+
Unlike the other actions where only a single method is called for each
request, I<every> auto action along the chain of namespaces will be
-called.
+called. Each C<auto> action will be called I<from the application/root
+controller down through the most specific class>.
=back
# This code illustrates how certain parts of the TT
# template will only be shown to users who have logged in
%]
- [% IF Catalyst.user %]
+ [% IF Catalyst.user_exists %]
Please Note: You are already logged in as '[% Catalyst.user.username %]'.
You can <a href="[% Catalyst.uri_for('/logout') %]">logout</a> here.
[% ELSE %]
<a href="[% Catalyst.uri_for('form_create') %]">Create</a>
</p>
-Reload your browser and you should now see a "Login" and "Create" links
-at the bottom of the page (as mentioned earlier, you can update
-template files without reloading the development server). Click this
-link to return to the login page. This time you I<should> see the
-"You are already logged in" message.
+Reload your browser and you should now see a "Login" and "Create" links
+at the bottom of the page (as mentioned earlier, you can update template
+files without reloading the development server). Click the first link
+to return to the login page. This time you I<should> see the "You are
+already logged in" message.
Finally, click the C<You can logout here> link on the C</login> page.
You should stay at the login page, but the message should change to "You
just avoiding the I<storage> of cleartext passwords in the database by
using a SHA-1 hash. If you are concerned about cleartext passwords
between the browser and your application, consider using SSL/TLS, made
-easy with the Catalyst plugin
-L<Catalyst::Plugin:RequireSSL|Catalyst::Plugin:RequireSSL>.
+easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
=head2 Get a SHA-1 Hash for the Password
# 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' to avoid a component lookup issue in Catalyst 5.66
+ # 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
login as before. When done, click the "Logout" link on the login page
(or point your browser at L<http://localhost:3000/logout>).
+B<Note:> If you receive the debug screen in your browser with a
+C<Can't call method "stash" on an undefined value...> error message,
+make sure that you are using v0.07 of
+L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>.
+The following command can be a useful way to quickly dump the version number
+of this module on your system:
+
+ perl -MCatalyst::Plugin::Authorization::ACL -e 'print $Catalyst::Plugin::Authorization::ACL::VERSION, "\n";'
+
+
+=head1 USING THE SESSION FOR FLASH
+
+As discussed in Part 3 of the tutorial, C<flash> allows you to set
+variables in a way that is very similar to C<stash>, but it will
+remain set across multiple requests. Once the value is read, it
+is cleared (unless reset). Although C<flash> has nothing to do with
+authentication, it does leverage the same session plugins. Now that
+those plugins are enabled, let's go back and improve the "delete
+and redirect with query parameters" code seen at the end of the
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD> part of the
+tutorial.
+
+First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete>
+to match the following:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete : Local {
+ # $id = primary key of book to delete
+ my ($self, $c, $id) = @_;
+
+ # Search for the book and then delete it
+ $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+
+ # 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 with status msg as an arg
+ $c->response->redirect($c->uri_for('/books/list'));
+ }
+
+Next, open C<root/lib/site/layout> and update the TT code to pull from
+flash vs. the C<status_msg> query parameter:
+
+ <div id="header">[% PROCESS site/header %]</div>
+
+ <div id="content">
+ <span class="message">[% status_msg || Catalyst.flash.status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+ [% content %]
+ </div>
+
+ <div id="footer">[% PROCESS site/footer %]</div>
+
+
+=head2 Try Out Flash
+
+Restart the development server and point your browser to
+L<http://localhost:3000/books/url_create/Test/1/4> to create an extra
+book. Click the "Return to list" link and delete the "Test" book you
+just added. The C<flash> mechanism should retain our "Book deleted"
+status message across the redirect.
+
+B<NOTE:> While C<flash> will save information across multiple requests,
+I<it does get cleared the first time it is read>. In general, this is
+exactly what you want -- the C<flash> message will get displayed on
+the next screen where it's appropriate, but it won't "keep showing up"
+after that first time (unless you reset it). Please refer to
+L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
+information.
+
=head1 AUTHOR
Kennedy Clark, C<hkclark@gmail.com>
Please report any errors, issues or suggestions to the author. The
-most recent version of the Catlayst Tutorial can be found at
+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