You can checkout the source code for this example from the catalyst
subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro>
+L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
=head1 BASIC AUTHENTICATION
option on the DBIC model helper to do most of the work for us:
$ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema create=static dbi:SQLite:myapp.db
+ exists "/root/dev/MyApp/script/../lib/MyApp/Model"
+ exists "/root/dev/MyApp/script/../t"
+ Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ...
+ Schema dump completed.
+ exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
+ $
$ ls lib/MyApp/Schema
Authors.pm BookAuthors.pm Books.pm Roles.pm UserRoles.pm Users.pm
files to the C<lib/MyApp/Schema/MyApp> directory. And, more
importantly, even if there were changes to the existing result source
files, those changes would have only been written above the C<# DO NOT
-MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-editted
+MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited
enhancements would have been preserved.
-
-Speaking of "hand-editted enhancements," we should now add
+Speaking of "hand-edit ted enhancements," we should now add
relationship information to the three new result source files. Edit
each of these files and add the following information between the C<#
DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>:
#
# Set relationships:
#
-
+
# has_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of the model class referenced by this relationship
# 3) Column name in *foreign* table
__PACKAGE__->has_many(map_user_role => 'MyApp::Schema::UserRoles', 'user_id');
-
+
# many_to_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
#
# Set relationships:
#
-
+
# has_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
#
# Set relationships:
#
-
+
# belongs_to():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
# 2) Name of the model class referenced by this relationship
# 3) Column name in *this* table
__PACKAGE__->belongs_to(user => 'MyApp::Schema::Users', 'user_id');
-
+
# belongs_to():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
Note that we do not need to make any change to the
C<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to
-load all of the result source files it finds in below the
+load all of the result class files it finds in below the
C<lib/MyApp/Schema> directory, so it will automatically pick
up our new table information.
'-------------------------------------------------------------------+----------'
...
-Again, notice that your "result source" classes have been "re-loaded"
+Again, notice that your "result class" classes have been "re-loaded"
by Catalyst under C<MyApp::Model>.
Edit C<lib/MyApp.pm> and update it as follows (everything below
C<StackTrace> is new):
- use Catalyst qw/
+ __PACKAGE__->setup(qw/
-Debug
ConfigLoader
Static::Simple
-
+
StackTrace
-
+
Authentication
-
+
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.
The C<Authentication> plugin supports Authentication while the
C<Session> plugins are required to maintain state across multiple HTTP
First, as noted in Part 3 of the tutorial, Catalyst has recently
switched from a default config file format of YAML to
-C<Config::General> (an apache-like format). In case you are using
-a version of Catalyst earlier than v5.7014, delete the C<myapp.yml>
-file and simply follow the directions below to create a new
-C<myapp.conf> file.
+C<Config::General> (an apache-like format). In case you are using a
+version of Catalyst earlier than v5.7014, delete the C<myapp.yml>, or
+convert it to .conf format using the TIP in
+L<Catalyst::Manual::MoreCatalystBasics>; then simply follow the
+directions below to create a new C<myapp.conf> file.
Here, we need to load several parameters that tell
L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
where to locate information in your database. To do this, edit the
C<myapp.conf> file and update it to match:
+ # rename this file to MyApp.yml and put a : in front of "name" if
+ # you want to use yaml like in old versions of Catalyst
name MyApp
<authentication>
default_realm dbic
# NOTE: Omit 'MyApp::Model' here just as you would when using
# '$c->model("DB::Users)'
user_class DB::Users
- # This is the name of the field in your 'users' table that
- # contains the user's name
- id_field username
</store>
</dbic>
</realms>
See L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
for details.
+
=head2 Add Login and Logout Controllers
Use the Catalyst create script to create two stub controller files:
Then open C<lib/MyApp/Controller/Login.pm>, locate the C<sub index
:Path :Args(0)> method (or C<sub index : Private> if you are using an
older version of Catalyst) that 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:
+helpers when we created the Login controller above, and update the
+definition of C<sub index> to match:
=head2 index
-
+
Login logic
-
+
=cut
-
+
sub index :Path :Args(0) {
my ($self, $c) = @_;
-
+
# Get the username and password from form
my $username = $c->request->params->{username} || "";
my $password = $c->request->params->{password} || "";
-
+
# If the username and password values were found in form
if ($username && $password) {
# Attempt to log the user in
if ($c->authenticate({ username => $username,
- password => $password} )) {
+ password => $password } )) {
# If successful, then let them use the application
$c->response->redirect($c->uri_for('/books/list'));
return;
$c->stash->{error_msg} = "Bad username or password.";
}
}
-
+
# If either of above don't work out, send to the login page
$c->stash->{template} = 'login.tt2';
}
C<username> and C<password> values are not present in the form, the
user will be taken to the empty login form.
-Note that we could have used something like C<sub default :Path>,
-however partly for historical reasons, and partly for code clarity it
-is generally recommended only to use C<default> in
-C<MyApp::Controller::Root>, and then mainly to generate the 404 not
+Note that we could have used something like C<sub default :Path>,
+however, it is generally recommended (partly for historical reasons,
+and partly for code clarity) only to use C<default> in
+C<MyApp::Controller::Root>, and then mainly to generate the 404 not
found page for the application.
Instead, we are using C<sub base :Path :Args(0) {...}> here to
C<lib/MyApp/Controller/Logout.pm> to match:
=head2 index
-
+
Logout logic
-
+
=cut
-
+
sub index :Path :Args(0) {
my ($self, $c) = @_;
-
+
# Clear the user's state
$c->logout;
-
+
# Send the user to the starting point
$c->response->redirect($c->uri_for('/'));
}
Create a login form by opening C<root/src/login.tt2> and inserting:
[% META title = 'Login' %]
-
+
<!-- Login form -->
- <form method="post" action="[% Catalyst.uri_for('/login') %]">
+ <form method="post" action="[% c.uri_for('/login') %]">
<table>
<tr>
<td>Username:</td>
the following method:
=head2 auto
-
+
Check if there is a user and, if not, forward to login page
-
+
=cut
-
+
# Note that 'auto' runs after 'begin' but before your actions and that
# 'auto's "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. This
- # allows anauthenticated users to reach any action in the Login
+ # allows unauthenticated 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 'index' action we
if ($c->controller eq $c->controller('Login')) {
return 1;
}
-
+
# If a user doesn't exist, force login
if (!$c->user_exists) {
# Dump a log message to the development server debug output
# Return 0 to cancel 'post-auto' processing and prevent use of application
return 0;
}
-
+
# User found, so return 1 to continue with processing after this 'auto'
return 1;
}
=item *
-The majority of application have traditionally used C<Local> actions
+The majority of applications have traditionally used C<Local> actions
for items that respond to user requests and C<Private> actions for
those that do not directly respond to user input.
# This code illustrates how certain parts of the TT
# template will only be shown to users who have logged in
%]
- [% 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.
+ [% IF c.user_exists %]
+ Please Note: You are already logged in as '[% c.user.username %]'.
+ You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
[% ELSE %]
You need to log in to use this application.
[% END %]
$ script/myapp_server.pl
-B<IMPORTANT NOTE:> If you are having issues with authentication on
-Internet Explorer, be sure to check the system clocks on both your
-server and client machines. Internet Explorer is very picky about
-timestamps for cookies. Note that you can quickly sync an Ubuntu
+B<IMPORTANT NOTE:> If you are having issues with authentication on
+Internet Explorer, be sure to check the system clocks on both your
+server and client machines. Internet Explorer is very picky about
+timestamps for cookies. Note that you can quickly sync an Ubuntu
system with the following command:
sudo ntpdate ntp.ubuntu.com
-Now trying going to L<http://localhost:3000/books/list> and you should
-be redirected to the login page, hitting Shift+Reload if necessary (the
-"You are already logged in" message should I<not> appear -- if it does,
-click the C<logout> button and try again). Note the C<***Root::auto User
-not found...> debug message in the development server output. Enter
-username C<test01> and password C<mypass>, and you should be taken to
-the Book List page.
+Or possibly try C<sudo ntpdate -u ntp.ubuntu.com> (to us an
+unpriviledged port) or C<sudo ntpdate pool.ntp.org> (to try a
+different server in case the Ubuntu NTP server is down).
+
+Now trying going to L<http://localhost:3000/books/list> and you should
+be redirected to the login page, hitting Shift+Reload or Ctrl+Reload
+if necessary (the "You are already logged in" message should I<not>
+appear -- if it does, click the C<logout> button and try again). Note
+the C<***Root::auto User not found...> debug message in the
+development server output. Enter username C<test01> and password
+C<mypass>, and you should be taken to the Book List page.
Open C<root/src/books/list.tt2> and add the following lines to the
bottom (below the closing </table> tag):
<p>
- <a href="[% Catalyst.uri_for('/login') %]">Login</a>
- <a href="[% Catalyst.uri_for('form_create') %]">Create</a>
+ <a href="[% c.uri_for('/login') %]">Login</a>
+ <a href="[% c.uri_for('form_create') %]">Create</a>
</p>
Reload your browser and you should now see a "Login" and "Create" links
$ sqlite3 myapp.db < myapp03.sql
-B<Note:> We are using SHA-1 hashes here, but many other hashing
+B<Note:> We are using SHA-1 hashes here, but many other hashing
algorithms are supported. See C<Digest> for more information.
Edit C<myapp.conf> and update it to match (the C<password_type> and
C<password_hash_type> are new, everything else is the same):
+ # rename this file to MyApp.yml and put a : in front of "name" if
+ # you want to use yaml like in old versions of Catalyst
name MyApp
<authentication>
default_realm dbic
# NOTE: Omit 'MyApp::Model' here just as you would when using
# '$c->model("DB::Users)'
user_class DB::Users
- # This is the name of the field in your 'users' table that
- # contains the user's name
- id_field username
</store>
</dbic>
</realms>
</authentication>
+
=head2 Try Out the Hashed Passwords
Press C<Ctrl-C> to kill the previous server instance (if it's still
has changed):
=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('DB::Books')->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
$c->response->redirect($c->uri_for('/books/list'));
}
-Next, open C<root/lib/site/layout> and update the TT code to pull from
+Next, open C<root/src/wrapper.tt2> 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>
+ [%# Status and error messages %]
+ <span class="message">[% status_msg || c.flash.status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+ [%# This is where TT will stick all of your template's contents. -%]
+ [% content %]
+ </div><!-- end content -->
+ ...
- <div id="footer">[% PROCESS site/footer %]</div>
+Although the sample above only shows the C<content> div, leave the
+rest of the file intact -- the only change we made to the C<wrapper.tt2>
+was to add "C<|| c.request.params.status_msg>" to the
+C<E<lt>span class="message"E<gt>> line.
=head2 Try Out Flash
L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
information.
+
=head2 Switch To Flash-To-Stash
Although the a use of flash above is certainly an improvement over the
-C<status_msg> we employed in Part 4 of the tutorial, the C<status_msg
-|| Catalyst.flash.status_msg> statement is a little ugly. A nice
+C<status_msg> we employed in Part 4 of the tutorial, the
+C<status_msg || c.flash.status_msg> statement is a little ugly. A nice
alternative is to use the C<flash_to_stash> feature that automatically
-copies the content of flash to stash. This makes your code controller
+copies the content of flash to stash. This makes your controller
and template code work regardless of where it was directly access, a
forward, or a redirect. To enable C<flash_to_stash>, you can either
set the value in C<lib/MyApp.pm> by changing the default
since it's not something you will want to change at runtime without it
possibly breaking some of your code.
-Then edit C<root/lib/site/layout> and change the C<status_msg> line
-to look like the following:
+Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line
+to match the following:
<span class="message">[% status_msg %]</span>
L<http://localhost:3000/books/list> in your browser. Delete another
of the "Test" books you added in the previous step. Flash should still
maintain the status message across the redirect even though you are no
-longer explicitly accessing C<Catalyst.flash>.
+longer explicitly accessing C<c.flash>.
=head1 AUTHOR
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-Manual/lib/Catalyst/Manual/Tutorial/>.
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).