Updates and additions to the tutorial.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Tutorial / Authentication.pod
index 0b06b2b..b330fd7 100644 (file)
@@ -21,7 +21,7 @@ L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
 
 =item 3
 
-L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
 
 =item 4
 
@@ -45,7 +45,7 @@ L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
 
 =item 9
 
-L<Appendices|Catalyst::Manual::Tutorial::Appendicies>
+L<Appendices|Catalyst::Manual::Tutorial::Appendices>
 
 =back
 
@@ -59,13 +59,9 @@ Part 5).
 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
 
@@ -333,7 +329,7 @@ Again, notice that your "result source" classes have been "re-loaded" by Catalys
 
 =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
@@ -341,7 +337,6 @@ Edit C<lib/MyApp.pm> and update it as follows (everything below C<DefaultEnd> is
             Static::Simple
             
             StackTrace
-            DefaultEnd
             
             Authentication
             Authentication::Store::DBIC
@@ -415,15 +410,21 @@ you could have a C<User> controller with both C<login> and C<logout>
 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.');
 
-    =head2 base
+Then update it to match:
+
+    =head2 index
     
     Login logic
     
     =cut
     
-    sub base :Path :Args(0) {
+    sub index : Private {
         my ($self, $c) = @_;
     
         # Get the username and password from form
@@ -454,29 +455,34 @@ at the login page but receive an error message.  If the C<username> and
 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>.
 
-    =head2 base
+Next, update the corresponding method in C<lib/MyApp/Controller/Logout.pm>
+to match:
+
+    =head2 index
     
     Logout logic
     
     =cut
     
-    sub base :Path :Args(0) {
+    sub index : Private {
         my ($self, $c) = @_;
     
         # Clear the user's state
@@ -486,8 +492,9 @@ Next, create a corresponding method in C<lib/MyApp/Controller/Logout.pm>:
         $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
@@ -534,11 +541,17 @@ the following method:
     
     # 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;
         }
     
@@ -576,9 +589,18 @@ C<default>, C<index>, and C<auto>.
 
 =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
 
@@ -599,7 +621,7 @@ lines to the bottom of the file:
        # 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 %]
@@ -655,11 +677,11 @@ bottom:
       <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
@@ -679,8 +701,7 @@ 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
 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
@@ -695,6 +716,13 @@ dirty" way to do this:
     e727d1464ae12436e899a726da5b2f11d8381b26
     $
 
+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 Switch to SHA-1 Password Hashes in the Database
 
@@ -734,7 +762,8 @@ C<password_hash_type> are new, everything else is the same):
             # 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
@@ -758,12 +787,88 @@ 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>).
 
+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
+C<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 util 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> 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 this "Test" book.  
+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,
+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.
+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-Runtime/lib/Catalyst/Manual/Tutorial/>.
 
 Copyright 2006, Kennedy Clark, under Creative Commons License
 (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).