authorization section, Chapter 6). Create a new SQL script file by opening
C<myapp02.sql> in your editor and insert:
+ PRAGMA foreign_keys = ON;
--
-- Add user and role tables, along with a many-to-many join table
--
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)
);
--
$ 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
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 ...
C<lib/MyApp/Schema/Result/User.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', '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');
+ __PACKAGE__->many_to_many(roles => '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');
-
-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.
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
/;
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
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::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; try
L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you
are on Win32) -- consult
L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses
to the following code:
<Plugin::Authentication>
- use_session 1
<default>
password_type clear
user_model DB::User
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) {
# Set an error message
$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
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:
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
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
$ 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:
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.
__PACKAGE__->config(
name => 'MyApp',
- session => {flash_to_stash => 1}
+ session => { flash_to_stash => 1 },
);
B<or> add the following to C<myapp.conf>: