[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