X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Manual.git;a=blobdiff_plain;f=lib%2FCatalyst%2FManual%2FTutorial%2FAuthentication.pod;h=3990f74f25f8d1b9669ba55af15aca41a15bdcf9;hp=2719cc43912d4b2923995cb99ae26b4e42239835;hb=15e1d0b201341bf72fbf027d1450bbddac49e80f;hpb=5edc2aaeb0c2dd02f1418b8e20c8a610d3779e29 diff --git a/lib/Catalyst/Manual/Tutorial/Authentication.pod b/lib/Catalyst/Manual/Tutorial/Authentication.pod index 2719cc4..3990f74 100644 --- a/lib/Catalyst/Manual/Tutorial/Authentication.pod +++ b/lib/Catalyst/Manual/Tutorial/Authentication.pod @@ -1,11 +1,11 @@ =head1 NAME -Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 4: Authentication +Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 5: Authentication =head1 OVERVIEW -This is B for the Catalyst tutorial. +This is B for the Catalyst tutorial. L @@ -21,30 +21,34 @@ L =item 3 -L +L =item 4 -B +L =item 5 -L +B =item 6 -L +L =item 7 -L +L =item 8 -L +L =item 9 +L + +=item 10 + L =back @@ -52,8 +56,8 @@ L =head1 DESCRIPTION -Now that we finally have a simple yet functional application, we can -focus on providing authentication (with authorization coming next in +Now that we finally have a simple yet functional application, we can +focus on providing authentication (with authorization coming next in Part 5). This part of the tutorial is divided into two main sections: 1) basic, @@ -117,58 +121,29 @@ Then load this into the C database with the following command: =head2 Add User and Role Information to DBIC Schema -This step adds DBIC-based classes for the user-related database tables -(the role information will not be used until Part 5): +Although we could manually edit the DBIC schema information to include +the new tables added in the previous step, let's use the C +option on the DBIC model helper to do most of the work for us: -Edit C and update the contents to match (only the -C [qw/Book BookAuthor Author User UserRole Role/]> line -has changed): + $ script/myapp_create.pl model MyAppDB DBIC::Schema MyApp::Schema::MyAppDB create=static dbi:SQLite:myapp.db + $ ls lib/MyApp/Schema/MyAppDB + Authors.pm BookAuthors.pm Books.pm Roles.pm UserRoles.pm Users.pm - package MyAppDB; - - =head1 NAME - - MyAppDB -- DBIC Schema Class - - =cut - - # Our schema needs to inherit from 'DBIx::Class::Schema' - use base qw/DBIx::Class::Schema/; - - # Need to load the DB Model classes here. - # You can use this syntax if you want: - # __PACKAGE__->load_classes(qw/Book BookAuthor Author User UserRole Role/); - # Also, if you simply want to load all of the classes in a directory - # of the same name as your schema class (as we do here) you can use: - # __PACKAGE__->load_classes(qw//); - # But the variation below is more flexible in that it can be used to - # load from multiple namespaces. - __PACKAGE__->load_classes({ - MyAppDB => [qw/Book BookAuthor Author User UserRole Role/] - }); - - 1; +Notice how the helper has added three new table-specific result source +files to the C 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 +enhancements would have been preserved. -=head2 Create New "Result Source Objects" +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;>: -Create the following three files with the content shown below. +C: -C: - - package MyAppDB::User; - - use base qw/DBIx::Class/; - - # Load required DBIC stuff - __PACKAGE__->load_components(qw/PK::Auto Core/); - # Set the table name - __PACKAGE__->table('users'); - # Set columns in table - __PACKAGE__->add_columns(qw/id username password email_address first_name last_name/); - # Set the primary key for the table - __PACKAGE__->set_primary_key('id'); - # # Set relationships: # @@ -178,41 +153,19 @@ C: # 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 => 'MyAppDB::UserRole', 'user_id'); - - - =head1 NAME - - MyAppDB::User - A model object representing a person with access to the system. - - =head1 DESCRIPTION + __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::MyAppDB::UserRoles', 'user_id'); - This is an object that represents a row in the 'users' table of your application - database. It uses DBIx::Class (aka, DBIC) to do ORM. - - For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. - Offline utilities may wish to use this class directly. - - =cut - - 1; + # 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_role', 'role'); -C: +C: - package MyAppDB::Role; - - use base qw/DBIx::Class/; - - # Load required DBIC stuff - __PACKAGE__->load_components(qw/PK::Auto Core/); - # Set the table name - __PACKAGE__->table('roles'); - # Set columns in table - __PACKAGE__->add_columns(qw/id role/); - # Set the primary key for the table - __PACKAGE__->set_primary_key('id'); - # # Set relationships: # @@ -222,42 +175,11 @@ C: # 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 => 'MyAppDB::UserRole', 'role_id'); - - - =head1 NAME - - MyAppDB::Role - A model object representing a class of access permissions to - the system. - - =head1 DESCRIPTION - - This is an object that represents a row in the 'roles' table of your - application database. It uses DBIx::Class (aka, DBIC) to do ORM. - - For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. - "Offline" utilities may wish to use this class directly. - - =cut - - 1; + __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::MyAppDB::UserRoles', 'role_id'); -C: +C: - package MyAppDB::UserRole; - - use base qw/DBIx::Class/; - - # Load required DBIC stuff - __PACKAGE__->load_components(qw/PK::Auto Core/); - # Set the table name - __PACKAGE__->table('user_roles'); - # Set columns in table - __PACKAGE__->add_columns(qw/user_id role_id/); - # Set the primary key for the table - __PACKAGE__->set_primary_key(qw/user_id role_id/); - # # Set relationships: # @@ -267,41 +189,33 @@ C: # 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 => 'MyAppDB::User', 'user_id'); + __PACKAGE__->belongs_to(user => 'MyApp::Schema::MyAppDB::Users', '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 => 'MyAppDB::Role', 'role_id'); - - - =head1 NAME - - MyAppDB::UserRole - A model object representing the JOIN between Users and Roles. - - =head1 DESCRIPTION - - This is an object that represents a row in the 'user_roles' table of your application - database. It uses DBIx::Class (aka, DBIC) to do ORM. - - You probably won't need to use this class directly -- it will be automatically - used by DBIC where joins are needed. - - For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. - Offline utilities may wish to use this class directly. - - =cut - - 1; + __PACKAGE__->belongs_to(role => 'MyApp::Schema::MyAppDB::Roles', 'role_id'); + + +The code for these three sets of updates is obviously very similar to +the edits we made to the C, C, and C +classes created in Part 3. -The code for these three result source classes is obviously very familiar to the C, C, and C classes created in Part 2. +Note that we do not need to make any change to the +C schema file. It simple tells DBIC to +load all of the result source files it finds in below the +C 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 to kill the previous server instance (if it's still running) and restart it: +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 to kill the previous server instance (if it's still running) +and restart it: $ script/myapp_server.pl @@ -315,21 +229,23 @@ Look for the three new model objects in the startup debug output: | MyApp::Controller::Root | instance | | MyApp::Model::MyAppDB | instance | | MyApp::Model::MyAppDB::Author | class | - | MyApp::Model::MyAppDB::Book | class | - | MyApp::Model::MyAppDB::BookAuthor | class | - | MyApp::Model::MyAppDB::Role | class | - | MyApp::Model::MyAppDB::User | class | - | MyApp::Model::MyAppDB::UserRole | class | + | MyApp::Model::MyAppDB::Books | class | + | MyApp::Model::MyAppDB::BookAuthors | class | + | MyApp::Model::MyAppDB::Roles | class | + | MyApp::Model::MyAppDB::Users | class | + | MyApp::Model::MyAppDB::UserRoles | class | | MyApp::View::TT | instance | '-------------------------------------------------------------------+----------' ... -Again, notice that your "result source" classes have been "re-loaded" by Catalyst under C. +Again, notice that your "result source" classes have been "re-loaded" +by Catalyst under C. =head2 Include Authentication and Session Plugins -Edit C and update it as follows (everything below C is new): +Edit C and update it as follows (everything below +C is new): use Catalyst qw/ -Debug @@ -339,66 +255,95 @@ Edit C and update it as follows (everything below C is StackTrace Authentication - Authentication::Store::DBIC - Authentication::Credential::Password Session Session::Store::FastMmap Session::State::Cookie /; -The three C plugins work together to support -Authentication while the C plugins are required to maintain -state across multiple HTTP requests. Note that there are several -options for L -(L -is generally a good choice if you are on Unix; try -L if you -are on Win32) -- consult -L and its subclasses -for additional information and options (for example to use a -database-backed session store). +The C plugin supports Authentication while the +C plugins are required to maintain state across multiple HTTP +requests. + +Note that the only required Authentication class is the main one. This +is a change that occurred in version 0.09999_01 of the +C plugin. You B to specify a particular +Authentication::Store or Authentication::Credential plugin. Instead, +indicate the Store and Credential you want to use in your application +configuration (see below). + +Note that there are several options for +L +(L +is generally a good choice if you are on Unix; try +L if you +are on Win32) -- consult +L and its subclasses +for additional information and options (for example to use a database- +backed session store). =head2 Configure Authentication -Although C<__PACKAGE__-Econfig(name =E 'value');> is still -supported, newer Catalyst applications tend to place all configuration -information in C and automatically load this information into -Cconfig> using the -L plugin. Here, we need -to load several parameters that tell -L +Although C<__PACKAGE__-Econfig(name =E 'value');> is still +supported, newer Catalyst applications tend to place all configuration +information in C and automatically load this information +into Cconfig> using the +L plugin. + +First, as noted in Part 3 of the tutorial, Catalyst has recently +switched from a default config file format of YAML to +C (an apache-like format). In case you are using +a version of Catalyst earlier than v5.7014, delete the C +file and simply follow the directions below to create a new +C file. + +Here, we need to load several parameters that tell +L where to locate information in your database. To do this, edit the -C YAML and update it to match: - - --- - name: MyApp - authentication: - dbic: - # Note this first definition would be the same as setting - # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User' - # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file). - # - # 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 - user_class: MyAppDB::User - # This is the name of the field in your 'users' table that contains the user's name - user_field: username - # This is the name of the field in your 'users' table that contains the password - password_field: password - # Other options can go here for hashed passwords +C file and update it to match: + + name MyApp + + default_realm dbic + + + + # 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 now + password_type clear + + + # 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 'MyAppDB::User' but as the + # Catalyst startup debug messages show, it was loaded as + # 'MyApp::Model::MyAppDB::Users'). + # NOTE: Omit 'MyApp::Model' here just as you would when using + # '$c->model("MyAppDB::Users)' + user_class MyAppDB::Users + # This is the name of the field in your 'users' table that + # contains the user's name + id_field username + + + + Inline comments in the code above explain how each field is being used. -B: Although YAML uses a very simple and easy-to-ready format, it -does require the use of a consistent level of indenting. Be sure you -line up everything on a given 'level' with the same number of indents. -Also, be sure not to use C characters (YAML does not support them -because they are handled inconsistently across editors). - +Note that you can use many other config file formats with catalyst. +See L +for details. =head2 Add Login and Logout Controllers @@ -436,7 +381,8 @@ Then update it to match: # If the username and password values were found in form if ($username && $password) { # Attempt to log the user in - if ($c->login($username, $password)) { + if ($c->authenticate({ username => $username, + password => $password} )) { # If successful, then let them use the application $c->response->redirect($c->uri_for('/books/list')); return; @@ -451,11 +397,11 @@ Then update it to match: } This controller fetches the C and C values from the -login form and attempts to perform a login. If successful, it redirects -the user to the book list page. If the login fails, the user will stay -at the login page but receive an error message. If the C and -C values are not present in the form, the user will be taken -to the empty login form. +login form and attempts to authenticate the user. If successful, it +redirects the user to the book list page. If the login fails, the user +will stay at the login page but receive an error message. If the +C and C 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; however, the use of C actions is discouraged because it does @@ -475,8 +421,8 @@ We make the match even more specific with the C<:Args(0)> action modifier -- this forces the match on I C, not C. -Next, update the corresponding method in C -to match: +Next, update the corresponding method in +C to match: =head2 index @@ -571,22 +517,41 @@ the following method: return 1; } -B Catalyst provides a number of different types of actions, such -as C, C, and C. You should refer to -L for a more detailed explanation, but the -following bullet points provide a quick introduction: + +B Catalyst provides a number of different types of actions, +such as C, C, C and the new C. You +should refer to L for +a more detailed explanation, but the following bullet points provide a +quick introduction: =over 4 =item * -The majority of application use C actions for items that respond -to user requests and C actions for those that do not directly -respond to user input. +The majority of application have traditionally use C actions +for items that respond to user requests and C actions for +those that do not directly respond to user input. + +=item * + +Newer Catalyst applications tend to use C actions and the +C attribute because of their power and flexibility. You can +specify the path to match relative to the namespace of the current +module as an argument to C. For example C in +C would match on the URL +C but C would +match on C. =item * -There are five types of C actions: C, C, +Automatic "chaining" of actions by the dispatcher is a powerful +feature that allows multiple methods to handle a single URL. See +L +for more information on chained actions. + +=item * + +There are five types of build-in C actions: C, C, C, C, and C. =item * @@ -636,7 +601,7 @@ lines to the bottom of the file: TT code, it's probably a little too subtle for use in "normal" comments. %] -

+

Although most of the code is comments, the middle few lines provide a "you are already logged in" reminder if the user returns to the login @@ -673,7 +638,7 @@ username C and password C, and you should be taken to the Book List page. Open C and add the following lines to the -bottom: +bottom (below the closing tag):

Login @@ -755,29 +720,43 @@ Edit C and update it to match (the C and C are new, everything else is the same): --- - name: MyApp - authentication: - dbic: - # Note this first definition would be the same as setting - # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User' - # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file). - # - # 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' 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 - # This is the name of the field in your 'users' table that contains the password - password_field: password - # Other options can go here for hashed passwords - # Enabled hashed passwords - password_type: hashed - # Use the SHA-1 hashing algorithm - password_hash_type: SHA-1 - + name MyApp + + default_realm dbic + + + + # 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 + + + # 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 'MyAppDB::User' but as the + # Catalyst startup debug messages show, it was loaded as + # 'MyApp::Model::MyAppDB::Users'). + # NOTE: Omit 'MyApp::Model' here just as you would when using + # '$c->model("MyAppDB::Users)' + user_class MyAppDB::Users + # This is the name of the field in your 'users' table that + # contains the user's name + id_field username + + + + =head2 Try Out the Hashed Passwords @@ -790,15 +769,6 @@ You should now be able to go to L and login as before. When done, click the "Logout" link on the login page (or point your browser at L). -B If you receive the debug screen in your browser with a -C error message, -make sure that you are using v0.07 of -L. -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 @@ -813,7 +783,8 @@ L part of the tutorial. First, open C and modify C -to match the following: +to match the following (everything after the model search line of code +has changed): =head2 delete @@ -826,12 +797,12 @@ to match the following: my ($self, $c, $id) = @_; # Search for the book and then delete it - $c->model('MyAppDB::Book')->search({id => $id})->delete_all; + $c->model('MyAppDB::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 with status msg as an arg + # Redirect the user back to the list page $c->response->redirect($c->uri_for('/books/list')); } @@ -852,10 +823,10 @@ flash vs. the C query parameter: =head2 Try Out Flash Restart the development server and point your browser to -L to create an extra -book. Click the "Return to list" link and delete the "Test" book you -just added. The C mechanism should retain our "Book deleted" -status message across the redirect. +L to create an extra +several books. Click the "Return to list" link and delete one of the +"Test" books you just added. The C mechanism should retain our +"Book deleted" status message across the redirect. B While C will save information across multiple requests, I. In general, this is @@ -865,6 +836,43 @@ after that first time (unless you reset it). Please refer to L for additional information. +=head2 Switch To Flash-To-Stash + +Although the a use of flash above is certainly an improvement over the +C we employed in Part 4 of the tutorial, the C statement is a little ugly. A nice +alternative is to use the C feature that automatically +copies the content of flash to stash. This makes your code controller +and template code work regardless of where it was directly access, a +forward, or a redirect. To enable C, you can either +set the value in C by changing the default +C<__PACKAGE__-Econfig> setting to something like: + + __PACKAGE__->config( + name => 'MyApp', + session => {flash_to_stash => 1} + ); + +B add the following to C: + + session: + flash_to_stash: 1 + +The C<__PACKAGE__-Econfig> option is probably preferable here +since it's not something you will want to change at runtime without it +possibly breaking some of your code. + +Then edit C and change the C line +to look like the following: + + [% status_msg %] + +Restart the development server and go to +L 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. + =head1 AUTHOR