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=0d7e7ab01c2a23ad7516e299b19501423620216a;hp=28e2501ad7fff3a4afa5b37585e228346f338c50;hb=3b1fa91be1d89d2297aa9e8e83462344d9cd9820;hpb=4f8ea4b998626d5e94d2042783ce8e955e03d2a5 diff --git a/lib/Catalyst/Manual/Tutorial/Authentication.pod b/lib/Catalyst/Manual/Tutorial/Authentication.pod index 28e2501..0d7e7ab 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 - Chapter 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 @@ -54,14 +58,15 @@ L Now that we finally have a simple yet functional application, we can focus on providing authentication (with authorization coming next in -Part 5). +Chapter 6). -This part of the tutorial is divided into two main sections: 1) basic, +This chapter of the tutorial is divided into two main sections: 1) basic, cleartext authentication and 2) hash-based authentication. You can checkout the source code for this example from the catalyst subversion repository as per the instructions in -L +L. + =head1 BASIC AUTHENTICATION @@ -73,13 +78,13 @@ application. First, we add both user and role information to the database (we will add the role information here although it will not be used until the -authorization section, Part 5). Create a new SQL script file by opening +authorization section, Chapter 6). Create a new SQL script file by opening C in your editor and insert: -- - -- Add users and roles tables, along with a many-to-many join table + -- Add user and role tables, along with a many-to-many join table -- - CREATE TABLE users ( + CREATE TABLE user ( id INTEGER PRIMARY KEY, username TEXT, password TEXT, @@ -88,11 +93,11 @@ C in your editor and insert: last_name TEXT, active INTEGER ); - CREATE TABLE roles ( + CREATE TABLE role ( id INTEGER PRIMARY KEY, role TEXT ); - CREATE TABLE user_roles ( + CREATE TABLE user_role ( user_id INTEGER, role_id INTEGER, PRIMARY KEY (user_id, role_id) @@ -100,75 +105,51 @@ C in your editor and insert: -- -- Load up some initial test data -- - INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1); - INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1); - INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0); - INSERT INTO roles VALUES (1, 'user'); - INSERT INTO roles VALUES (2, 'admin'); - INSERT INTO user_roles VALUES (1, 1); - INSERT INTO user_roles VALUES (1, 2); - INSERT INTO user_roles VALUES (2, 1); - INSERT INTO user_roles VALUES (3, 1); + INSERT INTO user VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1); + INSERT INTO user VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1); + INSERT INTO user VALUES (3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0); + INSERT INTO role VALUES (1, 'user'); + INSERT INTO role VALUES (2, 'admin'); + INSERT INTO user_role VALUES (1, 1); + INSERT INTO user_role VALUES (1, 2); + INSERT INTO user_role VALUES (2, 1); + INSERT INTO user_role VALUES (3, 1); Then load this into the C database with the following command: $ sqlite3 myapp.db < myapp02.sql - =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): - -Edit C and update the contents to match (only the -C [qw/Book BookAuthor Author User UserRole Role/]> line -has changed): - - 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; - +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: + + $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \ + create=static components=TimeStamp dbi:SQLite:myapp.db + 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 ... + Schema dump completed. + exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm" + $ + $ ls lib/MyApp/Schema/Result + Author.pm BookAuthor.pm Book.pm Role.pm User.pm UserRole.pm -=head2 Create New "Result Source Objects" +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-edited +enhancements would have been preserved. -Create the following three files with the content shown below. +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: +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: # @@ -177,42 +158,20 @@ C: # 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 - __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'user_id'); - + # 3) Column name in *foreign* table (aka, foreign key in peer table) + __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'user_id'); - =head1 NAME - - MyAppDB::User - A model object representing a person with access to the system. - - =head1 DESCRIPTION - - 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: # @@ -221,43 +180,12 @@ C: # 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 - __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; + # 3) Column name in *foreign* table (aka, foreign key in peer table) + __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', '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 +195,32 @@ 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::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 => '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::Result::Role', '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 Chapter 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 simply tells DBIC to load all +of the Result Class and ResultSet Class 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 @@ -313,102 +232,130 @@ Look for the three new model objects in the startup debug output: +-------------------------------------------------------------------+----------+ | MyApp::Controller::Books | instance | | 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::DB | instance | + | MyApp::Model::DB::Author | class | + | MyApp::Model::DB::Book | class | + | MyApp::Model::DB::BookAuthor | class | + | MyApp::Model::DB::Role | class | + | MyApp::Model::DB::User | class | + | MyApp::Model::DB::UserRole | 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 Class" 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): - - use Catalyst qw/ - -Debug - ConfigLoader - Static::Simple - - StackTrace - - Authentication - - Session - Session::Store::FastMmap - Session::State::Cookie - /; - -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 occured 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 +Edit C and update it as follows (everything below +C is new): + + # Load plugins + use Catalyst qw/-Debug + ConfigLoader + Static::Simple + + StackTrace + + Authentication + + Session + Session::Store::FastMmap + Session::State::Cookie + /; + +B As discussed in MoreCatalystBasics, different versions of +C have used a variety of methods to load the plugins. +You can put the plugins in the C statement if you prefer. + +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 +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', + ); + +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). +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 -where to locate information in your database. To do this, edit the -C YAML and update it to match: - - --- - name: MyApp - authentication: - default_realm: dbic - realms: - dbic: - credential: - class: Password - password_field: password - password_type: self_check - store: - 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::User'). - # NOTE: Omit 'MyApp::Model' to avoid a component lookup issue in Catalyst 5.66 - user_class: MyApp::Users - # This is the name of the field in your 'users' table that contains the user's name - id_field: username - role_relation: roles - role_field: rolename - ignore_fields_in_find: [ 'remote_name' ] - -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). +There are a variety of ways to provide configuration information to +L. +Here we will use +L +because it automatically sets a reasonable set of defaults for us. Open +C and place the following text above the call to +C<__PACKAGE__-Esetup();>: + + # Configure SimpleDB Authentication + __PACKAGE__->config->{'Plugin::Authentication'} = { + default => { + class => 'SimpleDB', + user_model => 'DB::User', + password_type => 'clear', + }, + }; + +We could have placed this configuration in C, but placing +it in C is probably a better place since it's not likely +something that users of your application will want to change during +deployment (or you could use a mixture: leave C and +C defined in C as we show above, but place +C in C to allow the type of password to be +easily modified during deployment). We will stick with putting +all of the authentication-related configuration in C +for the tutorial, but if you wish to use C, just convert +to the following code: + + + use_session 1 + + password_type self_check + user_model DB::User + class SimpleDB + + + +B Here is a short script that will dump the contents of +Cconfig> to L format in +C: + + $ perl -Ilib -e 'use MyApp; use Config::General; + Config::General->new->save_file("myapp.conf", MyApp->config);' + +B Because we are using SimpleDB along with a database layout +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. +Take a look at +C +for details. =head2 Add Login and Logout Controllers @@ -418,18 +365,16 @@ Use the Catalyst create script to create two stub controller files: $ script/myapp_create.pl controller Login $ script/myapp_create.pl controller Logout -B: You could easily use a single controller here. For example, -you could have a C controller with both C and C -actions. Remember, Catalyst is designed to be very flexible, and leaves -such matters up to you, the designer and programmer. +You could easily use a single controller here. For example, you could +have a C controller with both C and C actions. +Remember, Catalyst is designed to be very flexible, and leaves such +matters up to you, the designer and programmer. -Then open C, locate the C 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.'); - -Then update it to match: +Then open C, locate the +C method (or C if you +are using an older version of Catalyst) that was automatically +inserted by the helpers when we created the Login controller above, +and update the definition of C to match: =head2 index @@ -437,7 +382,7 @@ Then update it to match: =cut - sub index : Private { + sub index :Path :Args(0) { my ($self, $c) = @_; # Get the username and password from form @@ -447,9 +392,11 @@ 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->authenticate($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')); + $c->response->redirect($c->uri_for( + $c->controller('Books')->action_for('list'))); return; } else { # Set an error message @@ -461,33 +408,35 @@ Then update it to match: $c->stash->{template} = 'login.tt2'; } +Be sure to remove the C<$c-Eresponse-Ebody('Matched MyApp::Controller::Login in Login.');> +line of the C. + 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. - -Note that we could have used something like C; -however, the use of C actions is discouraged because it does -not receive path args as with other actions. The recommended practice -is to only use C in C. - -Another option would be to use something like -C (where the C<...> refers to the login -code shown in C above). We are using C here to specifically match the URL C. -C actions (aka, "literal actions") create URI matches relative to -the namespace of the controller where they are defined. Although -C supports arguments that allow relative and absolute paths to be -defined, here we use an empty C definition to match on just the -name of the controller itself. The method name, C, is arbitrary. -We make the match even more specific with the C<:Args(0)> action -modifier -- this forces the match on I C, not +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 and 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, it is generally recommended (partly for historical reasons, +and partly for code clarity) only to use C in +C, and then mainly to generate the 404 not +found page for the application. + +Instead, we are using "C" here to +specifically match the URL C. C actions (aka, "literal +actions") create URI matches relative to the namespace of the +controller where they are defined. Although C supports +arguments that allow relative and absolute paths to be defined, here +we use an empty C definition to match on just the name of the +controller itself. The method name, C, is arbitrary. 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 @@ -495,7 +444,7 @@ to match: =cut - sub index : Private { + sub index :Path :Args(0) { my ($self, $c) = @_; # Clear the user's state @@ -505,8 +454,8 @@ to match: $c->response->redirect($c->uri_for('/')); } -As with the login controller, be sure to delete the -C<$c->response->body('Matched MyApp::Controller::Logout in Logout.');> +As with the login controller, be sure to delete the +C<$c-Eresponse-Ebody('Matched MyApp::Controller::Logout in Logout.');> line of the C. @@ -517,7 +466,7 @@ Create a login form by opening C and inserting: [% META title = 'Login' %] -
+ @@ -553,16 +502,16 @@ the following method: =cut # Note that 'auto' runs after 'begin' but before your actions and that - # 'auto' "chain" (all from application path to most specific class are run) + # '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 anauthenticated users to reach any action in the Login + # allows unauthenticated 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 action we + # to only allow unauthenticated access to the 'index' action we # added above. if ($c->controller eq $c->controller('Login')) { return 1; @@ -582,44 +531,14 @@ 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: - -=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. - -=item * - -There are five types of C actions: C, C, -C, C, and C. - -=item * - -With C, C, C, C private actions, only the -most specific action of each type will be called. For example, if you -define a C action in your controller it will I a -C action in your application/root controller -- I the -action in your controller will be called. - -=item * - -Unlike the other actions where only a single method is called for each -request, I auto action along the chain of namespaces will be -called. Each C action will be called I. - -=back - -By placing the authentication enforcement code inside the C method -of C (or C), it will be -called for I request that is received by the entire application. +As discussed in +L, +every C method from the application/root controller down to the +most specific controller will be called. By placing the +authentication enforcement code inside the C method of +C (or C), it will be +called for I request that is received by the entire +application. =head2 Displaying Content Only to Authenticated Users @@ -629,25 +548,26 @@ changes depending on whether the user has authenticated yet. To do this, open C in your editor and add the following lines to the bottom of the file: + ...

[% - # This code illustrates how certain parts of the TT + # This code illustrates how certain parts of the TT # template will only be shown to users who have logged in %] - [% IF Catalyst.user_exists %] - Please Note: You are already logged in as '[% Catalyst.user.username %]'. - You can logout here. + [% IF c.user_exists %] + Please Note: You are already logged in as '[% c.user.username %]'. + You can logout here. [% ELSE %] You need to log in to use this application. [% END %] [%# Note that this whole block is a comment because the "#" appears - immediate after the "[%" (with no spaces in between). Although it - can be a handy way to temporarily "comment out" a whole block of - TT code, it's probably a little too subtle for use in "normal" + immediate after the "[%" (with no spaces in between). Although it + can be a handy way to temporarily "comment out" a whole block of + 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 @@ -663,38 +583,47 @@ running) and restart it: $ script/myapp_server.pl -B: If you happen to be using Internet Explorer, you may -need to use the command C
Username: