Revert "start work on single conroller"
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / 05_Authentication.pod
index 17bfa49..5c727c2 100644 (file)
@@ -84,6 +84,7 @@ C<myapp02.sql> in your editor and insert:
     --
     -- Add user and role tables, along with a many-to-many join table
     --
+    PRAGMA foreign_keys = ON;
     CREATE TABLE user (
             id            INTEGER PRIMARY KEY,
             username      TEXT,
@@ -98,8 +99,8 @@ C<myapp02.sql> in your editor and insert:
             role TEXT
     );
     CREATE TABLE user_role (
-            user_id INTEGER,
-            role_id INTEGER,
+            user_id INTEGER REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE,
+            role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
             PRIMARY KEY (user_id, role_id)
     );
     --
@@ -119,6 +120,7 @@ Then load this into the C<myapp.db> database with the following command:
 
     $ sqlite3 myapp.db < myapp02.sql
 
+
 =head2 Add User and Role Information to DBIC Schema
 
 Although we could manually edit the DBIC schema information to include
@@ -126,7 +128,8 @@ the new tables added in the previous step, let's use the C<create=static>
 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 components=TimeStamp dbi:SQLite:myapp.db
+        create=static components=TimeStamp dbi:SQLite:myapp.db \
+        on_connect_do="PRAGMA foreign_keys = ON"
      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 ...
@@ -136,77 +139,41 @@ option on the DBIC model helper to do most of the work for us:
     $ ls lib/MyApp/Schema/Result
     Author.pm  BookAuthor.pm  Book.pm  Role.pm  User.pm  UserRole.pm
 
-Notice how the helper has added three new table-specific result source
+Notice how the helper has added three new table-specific Result Source
 files to the C<lib/MyApp/Schema/Result> 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-edited
 enhancements would have been preserved.
 
-Speaking of "hand-editted 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;>:
-
-C<lib/MyApp/Schema/Result/User.pm>:
+Speaking of "hand-editted enhancements," we should now add the 
+C<many_to_many> relationship information to the User Result Source file. 
+As with the Book, BookAuthor, and Author files in 
+L<Chapter 3|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>, 
+L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> has 
+automatically created the C<has_many> and C<belongs_to> relationships 
+for the new User, UserRole, and Role tables. However, as a convenience 
+for mapping Users to their assigned roles (see 
+L<Chapter 6|Catalyst::Manual::Tutorial::06_Authorization>), we will 
+also manually add a C<many_to_many> relationship. Edit 
+C<lib/MyApp/Schema/Result/User.pm> 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 (aka, foreign key in peer table)
-    __PACKAGE__->has_many(map_user_roles => 'MyApp::Schema::Result::UserRole', 'user_id');
-    
     # many_to_many():
     #   args:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of has_many() relationship this many_to_many() is shortcut for
     #     3) Name of belongs_to() relationship in model class of has_many() above
     #   You must already have the has_many() defined to use a many_to_many().
-    __PACKAGE__->many_to_many(roles => 'map_user_roles', 'role');
-
-
-C<lib/MyApp/Schema/Result/Role.pm>:
-
-    #
-    # 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_roles => 'MyApp::Schema::Result::UserRole', 'role_id');
-
-
-C<lib/MyApp/Schema/Result/UserRole.pm>:
-
-    #
-    # 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::User', 'user_id');
-    
-    # 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(role => 'MyApp::Schema::Result::Role', 'role_id');
+    __PACKAGE__->many_to_many(roles => 'user_roles', 'role');
 
-The code for these three sets of updates is obviously very similar to
-the edits we made to the C<Book>, C<Author>, and C<BookAuthor>
-classes created in Chapter 3.
+The code for this update is obviously very similar to the edits we made 
+to the C<Book> and C<Author> classes created in Chapter 3 with one 
+exception: we only defined the C<many_to_many> relationship in one 
+direction. Whereas we felt that we would want to map Authors to Books 
+B<AND> Books to Authors, here we are only adding the convenience 
+C<many_to_many> in the Users to Roles direction. 
 
 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
@@ -215,16 +182,15 @@ C<lib/MyApp/Schema> directory, so it will automatically pick up our
 new table information.
 
 
-=head2 Sanity-Check Reload of Development Server
-
-We aren't ready to try out the authentication just yet; we only want
-to do a quick check to be sure our model loads correctly.  Press
-C<Ctrl-C> to kill the previous server instance (if it's still running)
-and restart it:
+=head2 Sanity-Check of the Development Server Reload
 
-    $ script/myapp_server.pl
-
-Look for the three new model objects in the startup debug output:
+We aren't ready to try out the authentication just yet; we only want to 
+do a quick check to be sure our model loads correctly. Assuming that you 
+are following along and using the "-r" option on C<myapp_server.pl>, 
+then the development server should automatically reload (if not, press 
+C<Ctrl-C> to break out of the server if it's running and then enter 
+C<script/myapp_server.pl> to start it). Look for the three new model 
+objects in the startup debug output: 
 
     ...
      .-------------------------------------------------------------------+----------.
@@ -254,22 +220,23 @@ C<StackTrace> is new):
 
     # Load plugins
     use Catalyst qw/
-                    -Debug
-                    ConfigLoader
-                    Static::Simple
+        -Debug
+        ConfigLoader
+        Static::Simple
     
-                    StackTrace
+        StackTrace
     
-                    Authentication
+        Authentication
     
-                    Session
-                    Session::Store::FastMmap
-                    Session::State::Cookie
-                    /;
+        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.
+C<Catalyst::Devel> have used a variety of methods to load the plugins,
+but we are going to use the current Catalyst 5.8X practice of putting
+them on the C<use Catalyst> line.
 
 The C<Authentication> plugin supports Authentication while the
 C<Session> plugins are required to maintain state across multiple HTTP
@@ -285,19 +252,18 @@ configuration (see below).
 Make sure you include the additional plugins as new dependencies in
 the Makefile.PL file something like this:
 
-    requires (
-        'Catalyst::Plugin::Authentication' => '0',
-        'Catalyst::Plugin::Session' => '0',
-        'Catalyst::Plugin::Session::Store::FastMmap' => '0',
-        'Catalyst::Plugin::Session::State::Cookie' => '0',
-    );
+    requires 'Catalyst::Plugin::Authentication';
+    requires 'Catalyst::Plugin::Session';
+    requires 'Catalyst::Plugin::Session::Store::FastMmap';
+    requires 'Catalyst::Plugin::Session::State::Cookie';
 
 Note that there are several options for
-L<Session::Store|Catalyst::Plugin::Session::Store>
-(L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap>
-is generally a good choice if you are on Unix; try
-L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you
-are on Win32) -- consult
+L<Session::Store|Catalyst::Plugin::Session::Store>.
+L<Session::Store::Memcached|Catalyst::Plugin::Session::Store::Memcached> or
+L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap> is
+generally a good choice if you are on Unix.  If you are running on
+Windows, try
+L<Session::Store::File|Catalyst::Plugin::Session::Store::File>. Consult
 L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses
 for additional information and options (for example to use a database-
 backed session store).
@@ -334,9 +300,8 @@ for the tutorial, but if you wish to use C<myapp.conf>, just convert
 to the following code:
 
     <Plugin::Authentication>
-        use_session 1
         <default>
-            password_type self_check
+            password_type clear
             user_model    DB::User
             class         SimpleDB
         </default>
@@ -346,11 +311,15 @@ B<TIP:> Here is a short script that will dump the contents of
 C<MyApp->config> to L<Config::General|Config::General> format in
 C<myapp.conf>:
 
-    $ perl -Ilib -e 'use MyApp; use Config::General; 
+    $ CATALYST_DEBUG=0 perl -Ilib -e 'use MyApp; use Config::General; 
         Config::General->new->save_file("myapp.conf", MyApp->config);'
 
+B<HOWEVER>, if you try out the command above, be sure to delete the
+"myapp.conf" command.  Otherwise, you will wind up with duplicate
+configurations.
+
 B<NOTE:> Because we are using SimpleDB along with a database layout 
-that complies with its default assumptions, we don't need to specify
+that complies with its default assumptions: we don't need to specify
 the names of the columns where our username and password information
 is stored (hence, the "Simple" part of "SimpleDB").  That being said,
 SimpleDB lets you specify that type of information if you need to.
@@ -387,8 +356,8 @@ and update the definition of C<sub index> to match:
         my ($self, $c) = @_;
     
         # Get the username and password from form
-        my $username = $c->request->params->{username} || "";
-        my $password = $c->request->params->{password} || "";
+        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) {
@@ -401,15 +370,19 @@ and update the definition of C<sub index> to match:
                 return;
             } else {
                 # Set an error message
-                $c->stash->{error_msg} = "Bad username or password.";
+                $c->stash(error_msg => "Bad username or password.");
             }
+        } else {
+            # Set an error message
+            $c->stash(error_msg => "Empty username or password.");
         }
     
         # If either of above don't work out, send to the login page
-        $c->stash->{template} = 'login.tt2';
+        $c->stash(template => 'login.tt2');
     }
 
-Be sure to remove the C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');>
+Be sure to remove the 
+C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');>
 line of the C<sub index>.
 
 This controller fetches the C<username> and C<password> values from the
@@ -489,9 +462,8 @@ Create a login form by opening C<root/src/login.tt2> and inserting:
 We need something that provides enforcement for the authentication
 mechanism -- a I<global> mechanism that prevents users who have not
 passed authentication from reaching any pages except the login page.
-This is generally done via an C<auto> action/method (prior to Catalyst
-v5.66, this sort of thing would go in C<MyApp.pm>, but starting in
-v5.66, the preferred location is C<lib/MyApp/Controller/Root.pm>).
+This is generally done via an C<auto> action/method in 
+C<lib/MyApp/Controller/Root.pm>.
 
 Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert
 the following method:
@@ -505,7 +477,7 @@ the following method:
     # 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 {
+    sub auto :Private {
         my ($self, $c) = @_;
     
         # Allow unauthenticated users to reach the login page.  This
@@ -579,10 +551,15 @@ use of an IF-THEN-ELSE construct in TT).
 
 =head2 Try Out Authentication
 
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
-    $ script/myapp_server.pl
+The development server should have reloaded each time we edited one of 
+the Controllers in the previous section. 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. 
 
 B<IMPORTANT NOTE:> If you are having issues with authentication on
 Internet Explorer, be sure to check the system clocks on both your
@@ -605,17 +582,10 @@ UDP vs. the more common TCP that you see with most Internet protocols.
 Worse case, you might have to manually set the time on your development
 box instead of using NTP.
 
-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="[% c.uri_for('/login') %]">Login</a>
       <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
@@ -623,7 +593,7 @@ bottom (below the closing </table> tag):
 
 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
+files without a development server reload).  Click the first link
 to return to the login page.  This time you I<should> see the "You are
 already logged in" message.
 
@@ -650,16 +620,6 @@ between the browser and your application, consider using SSL/TLS, made
 easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
 
 
-=head2 Install DBIx::Class::EncodedColumn
-
-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:
-
-    $ sudo cpan DBIx::Class::EncodedColumn
-
-
 =head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::EncodedColumn
 
 Next, we can re-run the model helper to have it include 
@@ -669,14 +629,15 @@ saw in Chapters 3 and 4, but add C<,EncodedColumn> to the C<components>
 argument:
 
     $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
-        create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db
+        create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db \
+        on_connect_do="PRAGMA foreign_keys = ON"
 
 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/User.pm> since that's the main class where we
 want to use hashed and salted passwords:
 
-    __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
+    __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn");
 
 
 =head2 Modify the "password" Column to Use EncodedColumn
@@ -689,8 +650,6 @@ the closing "1;":
     # 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},
@@ -748,16 +707,20 @@ Then run the following command:
 
     $ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl
 
-We had to use the C<-Ilib> arguement to tell perl to look under the 
+We had to use the C<-Ilib> argument to tell perl to look under the 
 C<lib> directory for our C<MyApp::Schema> model.
 
 The DBIC_TRACE output should show that the update worked:
 
     $ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl
-    SELECT me.id, me.username, me.password, me.email_address, me.first_name, me.last_name, me.active FROM user me: 
-    UPDATE user SET password = ? WHERE ( id = ? ): 'oXiyAcGOjowz7ISUhpIm1IrS8AxSZ9r4jNjpX9VnVeQmN6GRtRKTz', '1'
-    UPDATE user SET password = ? WHERE ( id = ? ): 'PmyEPrkB8EGwvaF/DvJm7LIfxoZARjv8ygFIR7pc1gEA1OfwHGNzs', '2'
-    UPDATE user SET password = ? WHERE ( id = ? ): 'h7CS1Fm9UCs4hjcbu2im0HumaHCJUq4Uriac+SQgdUMUfFSoOrz3c', '3'
+    SELECT me.id, me.username, me.password, me.email_address, 
+    me.first_name, me.last_name, me.active FROM user me: 
+    UPDATE user SET password = ? WHERE ( id = ? ): 
+    'oXiyAcGOjowz7ISUhpIm1IrS8AxSZ9r4jNjpX9VnVeQmN6GRtRKTz', '1'
+    UPDATE user SET password = ? WHERE ( id = ? ): 
+    'PmyEPrkB8EGwvaF/DvJm7LIfxoZARjv8ygFIR7pc1gEA1OfwHGNzs', '2'
+    UPDATE user SET password = ? WHERE ( id = ? ): 
+    'h7CS1Fm9UCs4hjcbu2im0HumaHCJUq4Uriac+SQgdUMUfFSoOrz3c', '3'
 
 But we can further confirm our actions by dumping the users table:
 
@@ -767,15 +730,17 @@ But we can further confirm our actions by dumping the users table:
     3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03@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 
+database (not only are the hashes stored, but every hash is different 
+even though the passwords are the same because of the added "salt" 
+value).  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):
+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'} = {
@@ -793,14 +758,11 @@ 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
-running) and restart it:
-
-    $ script/myapp_server.pl
-
-You should now be able to go to L<http://localhost:3000/books/list> and
-login as before.  When done, click the "logout" link on the login page
-(or point your browser at L<http://localhost:3000/logout>).
+The development server should restart as soon as your save the 
+C<lib/MyApp.pm> file in the previous section. You should now be able to 
+go to L<http://localhost:3000/books/list> and login as before. When 
+done, click the "logout" link on the login page (or point your browser 
+at L<http://localhost:3000/logout>). 
 
 
 =head1 USING THE SESSION FOR FLASH
@@ -853,14 +815,14 @@ flash vs. the C<status_msg> query parameter:
     ...
 
 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.
+rest of the file intact -- the only change we made to replace 
+"|| c.request.params.status_msg" with "c.flash.status_msg" in the 
+C<< <span class="message"> >> line.
 
 
 =head2 Try Out Flash
 
-Restart the development server, log in, and then point your browser to
+Authenticate using the login screen and then point your browser to
 L<http://localhost:3000/books/url_create/Test/1/4> to create an extra
 several books.  Click the "Return to list" link and delete one of the
 "Test" books you just added.  The C<flash> mechanism should retain our
@@ -888,7 +850,9 @@ C<__PACKAGE__-E<gt>config> setting to something like:
 
     __PACKAGE__->config(
             name    => 'MyApp',
-            session => {flash_to_stash => 1}
+            # Disable deprecated behavior needed by old applications
+            disable_component_resolution_regex_fallback => 1,
+            session => { flash_to_stash => 1 },
         );
 
 B<or> add the following to C<myapp.conf>:
@@ -906,11 +870,10 @@ to match the following:
 
     <span class="message">[% status_msg %]</span>
 
-Restart the development server and go to
-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<c.flash>.
+Now go to 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<c.flash>. 
 
 
 =head1 AUTHOR