[Catalyst-commits] r9989 -
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial
hkclark at dev.catalyst.perl.org
hkclark at dev.catalyst.perl.org
Sun May 3 02:40:36 GMT 2009
Author: hkclark
Date: 2009-05-03 02:40:36 +0000 (Sun, 03 May 2009)
New Revision: 9989
Modified:
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod
Log:
Switch to use of DBIx::Class::EncodedColumn and C::Auth::Realm::SimpleDB
Modified: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod 2009-05-02 03:24:56 UTC (rev 9988)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod 2009-05-03 02:40:36 UTC (rev 9989)
@@ -154,14 +154,14 @@
#
# 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 (aka, foreign key in peer table)
__PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'user_id');
-
+
# many_to_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
@@ -176,7 +176,7 @@
#
# Set relationships:
#
-
+
# has_many():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
@@ -190,14 +190,14 @@
#
# 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::Result::Users', 'user_id');
-
+
# belongs_to():
# args:
# 1) Name of relationship, DBIC will create accessor with this name
@@ -258,11 +258,11 @@
use Catalyst qw/-Debug
ConfigLoader
Static::Simple
-
+
StackTrace
-
+
Authentication
-
+
Session
Session::Store::FastMmap
Session::State::Cookie
@@ -296,69 +296,29 @@
=head2 Configure Authentication
-Although C<__PACKAGE__-E<gt>config(name =E<gt> 'value');> is still
-supported, newer Catalyst applications tend to place all configuration
-information in C<myapp.conf> and automatically load this information
-into C<MyApp-E<gt>config> using the
-L<ConfigLoader|Catalyst::Plugin::ConfigLoader> plugin.
+There are a variety of way to provide configuration information to
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>.
+Here we will use
+L<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
+because it automatically sets a reasonable set of defaults for us. Open
+C<lib/MyApp.pm> and place the following text above the call to
+C<__PACKAGE__-E<gt>setup();>:
-As discussed in Chapter 3 of the tutorial, Catalyst has recently
-switched from a default config file format of YAML to
-L<Config::General|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/EDIT THE LIST OF CATALYST PLUGINS>
-then simply follow the directions below to create a new C<myapp.conf>
-file. Although we will use the C<Config::General> format here because
-YAML files can be difficult to cut and paste in certain environments,
-you are free to use any format supported by
-L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> and
-L<Config::Any|Config::Any> -- Catalyst will transparently handle the
-different formats.
+ # Configure SimpleDB Authentication
+ __PACKAGE__->config->{'Plugin::Authentication'} = {
+ default => {
+ class => 'SimpleDB',
+ user_model => 'DB::Users',
+ password_type => 'clear',
+ },
+ };
-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:
+We could have placed this configuration in C<myapp.conf>, but placing
+it in C<lib/MyApp.pm> is probably a better place since it's not likely
+something that users of your application will want to change during
+deployment.
- # 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
- <realms>
- <dbic>
- <credential>
- # Note: this first definition would be the same as setting
- # __PACKAGE__->config->{authentication}->{realms}->{dbic}
- # ->{credential} = 'Password' in lib/MyApp.pm
- #
- # Specify that we are going to do password-based auth
- class Password
- # This is the name of the field in the users table with the
- # password stored in it
- password_field password
- # We are using an unencrypted password for now
- password_type clear
- </credential>
- <store>
- # Use DBIC to retrieve username, password & role information
- class DBIx::Class
- # This is the model object created by Catalyst::Model::DBIC
- # from your schema (you created 'MyApp::Schema::Result::User'
- # but as the Catalyst startup debug messages show, it was
- # loaded as 'MyApp::Model::DB::Users').
- # NOTE: Omit 'MyApp::Model' here just as you would when using
- # '$c->model("DB::Users)'
- user_class DB::Users
- </store>
- </dbic>
- </realms>
- </authentication>
-Inline comments in the code above explain how each field is being used.
-
-
=head2 Add Login and Logout Controllers
Use the Catalyst create script to create two stub controller files:
@@ -378,18 +338,18 @@
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
@@ -404,7 +364,7 @@
$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';
}
@@ -437,17 +397,17 @@
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('/'));
}
@@ -462,7 +422,7 @@
Create a login form by opening C<root/src/login.tt2> and inserting:
[% META title = 'Login' %]
-
+
<!-- Login form -->
<form method="post" action="[% c.uri_for('/login') %]">
<table>
@@ -494,17 +454,17 @@
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 unauthenticated users to reach any action in the Login
# controller. To lock it down to a single action, we could use:
@@ -514,7 +474,7 @@
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
@@ -524,7 +484,7 @@
# 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;
}
@@ -631,8 +591,10 @@
=head1 USING PASSWORD HASHES
-In this section we increase the security of our system by converting
-from cleartext passwords to SHA-1 password hashes.
+In this section we increase the security of our system by converting
+from cleartext passwords to SHA-1 password hashes that include a
+random "salt" value to make them extremely difficult to crack with
+dictionary and "rainbow table" attacks.
B<Note:> This section is optional. You can skip it and the rest of the
tutorial will function normally.
@@ -640,98 +602,144 @@
Be aware that even with the techniques shown in this section, the browser
still transmits the passwords in cleartext to your application. We are
just avoiding the I<storage> of cleartext passwords in the database by
-using a SHA-1 hash. If you are concerned about cleartext passwords
+using a salted 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 Catalyst::Plugin:RequireSSL. You should
-also consider adding a "salt" mechanism to your hashed passwords to
-mitigate the risk of a "rainbow table" crack against your passwords (see
-L<Catalyst::Authentication::Credential::Password|Catalyst::Authentication::Credential::Password>
-for more information on using a salt value).
+easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
-=head2 Get a SHA-1 Hash for the Password
+=head2 Install DBIx::Class::EncodedColumn
-Catalyst uses the C<Digest> module to support a variety of hashing
-algorithms. Here we will use SHA-1 (SHA = Secure Hash Algorithm).
-First, we should compute the SHA-1 hash for the "mypass" password we are
-using. The following command-line Perl script provides a "quick and
-dirty" way to do this:
+L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> provides features
+that can greatly simplify the maintenance of passwords. It's currently
+not available as a .deb package in the normal Debian repositories, so let's
+install it directly from CPAN:
- $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"'
- e727d1464ae12436e899a726da5b2f11d8381b26
+ $ sudo cpan DBIx::Class::EncodedColumn
-B<Note:> You should probably modify this code for production use to
-not read the password from the command line. By having the script
-prompt for the cleartext password, it avoids having the password linger
-in forms such as your C<.bash_history> files (assuming you are using
-BASH as your shell). An example of such a script can be found in
-Appendix 3.
+=head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::EncodedColumn
-=head2 Switch to SHA-1 Password Hashes in the Database
+Next, we can re-run the model helper to have it include
+L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> in all of the
+Result Classes it generates for us. Simply use the same command we
+saw in Chapters 3 and 4, but add C<,EncodedColumn> to the C<components>
+argument:
-Next, we need to change the C<password> column of our C<users> table to
-store this hash value vs. the existing cleartext password. Open
-C<myapp03.sql> in your editor and enter:
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db
- --
- -- Convert passwords to SHA-1 hashes
- --
- UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1;
- UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2;
- UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3;
+If you then open one of the Result Classes, you will see that it
+includes EncodedColumn in the C<load_components> line. Take a look at
+C<lib/MyApp/Schema/Result/Users.pm> since that's the main class where we
+want to use hashed and salted passwords:
-Then use the following command to update the SQLite database:
+ __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
- $ sqlite3 myapp.db < myapp03.sql
-B<Note:> We are using SHA-1 hashes here, but many other hashing
-algorithms are supported. See C<Digest> for more information.
+=head2 Modify the "password" Column to Use EncodedColumn
+Open the file C<lib/MyApp/Schema/Result/Users.pm> and enter the following
+text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
+the closing "1;":
-=head2 Enable SHA-1 Hash Passwords in Catalyst::Plugin::Authentication::Store::DBIC
+ # Have the 'password' column use a SHA-1 hash and 10-character salt
+ # with hex encoding; Generate the 'check_password" method
+ __PACKAGE__->add_columns(
+ 'password' => {
+ data_type => "TEXT",
+ size => undef,
+ encode_column => 1,
+ encode_class => 'Digest',
+ encode_args => {salt_length => 10},
+ encode_check_method => 'check_password',
+ },
+ );
-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):
+This redefines the automatically generated definition for the password
+fields at the top of the Result Class file to now use EncodedColumn
+logic (C<encoded_column> is set to 1). C<encode_class> can be set to
+either C<Digest> to use
+L<DBIx::Class::EncodedColumn::Digest|DBIx::Class::EncodedColumn::Digest>,
+or C<Crypt::Eksblowfish::Bcrypt> for
+L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt|DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt>.
+C<encode_args> is then used to customize the type of Digest you
+selected. Here we only specified the size of the salt to use, but
+we could have also modified the hashing algorithm ('SHA-256' is
+the default) and the format to use ('base64' is the default, but
+'hex' and 'binary' are other options). To use these, you could
+change the C<encode_args> to something like:
- # 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
- <realms>
- <dbic>
- <credential>
- # Note this first definition would be the same as setting
- # __PACKAGE__->config->{authentication}->{realms}->{dbic}
- # ->{credential} = 'Password' in lib/MyApp.pm
- #
- # Specify that we are going to do password-based auth
- class Password
- # This is the name of the field in the users table with the
- # password stored in it
- password_field password
- # Switch to more secure hashed passwords
- password_type hashed
- # Use the SHA-1 hashing algorithm
- password_hash_type SHA-1
- </credential>
- <store>
- # Use DBIC to retrieve username, password & role information
- class DBIx::Class
- # This is the model object created by Catalyst::Model::DBIC
- # from your schema (you created 'MyApp::Schema::Result::User'
- # but as the Catalyst startup debug messages show, it was
- # loaded as 'MyApp::Model::DB::Users').
- # NOTE: Omit 'MyApp::Model' here just as you would when using
- # '$c->model("DB::Users)'
- user_class DB::Users
- </store>
- </dbic>
- </realms>
- </authentication>
+ encode_args => {algorithm => 'SHA-1',
+ format => 'hex',
+ salt_length => 10},
+=head2 Load Hashed Passwords in the Database
+
+Next, let's create a quick script to load some hashed and salted passwords
+into the C<password> column of our C<users> table. Open the file
+C<set_hashed_passwords.pl> in your editor and enter the following text:
+
+ #!/usr/bin/perl
+
+ use strict;
+ use warnings;
+
+ use MyApp::Schema;
+
+ my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
+
+ my @users = $schema->resultset('Users')->all;
+
+ foreach my $user (@users) {
+ $user->password('mypass');
+ $user->update;
+ }
+
+EncodedColumn lets us simple call C<$user->check_password($password)>
+to see if the user has supplied the correct password, or, as we show
+above, call C<$user->update($new_password)> to update the hashed
+password stored for this user.
+
+Then run the following command:
+
+ $ perl -Ilib set_hashed_passwords.pl
+
+We had to use the C<-Ilib> arguement to tell perl to look under the
+C<lib> directory for our C<MyApp::Schema> model.
+
+Then dump the users table to verify that it worked:
+
+ $ sqlite3 myapp.db "select * from users"
+ 1|test01|38d3974fa9e9263099f7bc2574284b2f55473a9bM=fwpX2NR8|t01 at na.com|Joe|Blow|1
+ 2|test02|6ed8586587e53e0d7509b1cfed5df08feadc68cbMJlnPyPt0I|t02 at na.com|Jane|Doe|1
+ 3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03 at na.com|No|Go|0
+
+As you can see, the passwords are much harder to steal from the
+database. Also note that this demonstrates how to use a DBIx::Class
+model outside of your web application -- a very useful feature in many
+situations.
+
+
+=head2 Enable Hashed and Salted Passwords
+
+Edit C<lib/MyApp.pm> and update it to match the following text (the only change
+is to the C<password_type> field):
+
+ # Configure SimpleDB Authentication
+ __PACKAGE__->config->{'Plugin::Authentication'} = {
+ default => {
+ class => 'SimpleDB',
+ user_model => 'DB::Users',
+ password_type => 'self_check',
+ },
+ };
+
+The use of C<self_check> will cause
+Catalyst::Plugin::Authentication::Store::DBIC to call the
+C<check_password> method we enabled on our C<password> columns.
+
+
=head2 Try Out the Hashed Passwords
Press C<Ctrl-C> to kill the previous server instance (if it's still
@@ -761,21 +769,21 @@
has changed):
=head2 delete
-
+
Delete a book
-
+
=cut
-
+
sub delete :Chained('object') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
-
+
# 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')));
}
@@ -828,7 +836,7 @@
C<__PACKAGE__-E<gt>config> setting to something like:
__PACKAGE__->config(
- name => 'MyApp',
+ name => 'MyApp',
session => {flash_to_stash => 1}
);
Modified: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod 2009-05-02 03:24:56 UTC (rev 9988)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod 2009-05-03 02:40:36 UTC (rev 9989)
@@ -598,7 +598,7 @@
with the following commands:
sudo cpan Catalyst::Model::DBIC::Schema Time::Warp DBICx::TestDatabase \
- DBIx::Class::DynamicDefault DBIx::Class::TimeStamp
+ DBIx::Class::DynamicDefault DBIx::Class::TimeStamp DBIx::Class::EncodedColumn
wget http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/MyApp_Chapter8.tgz
tar zxvf MyApp_Chapter8.tgz
cd MyApp
More information about the Catalyst-commits
mailing list