Updating Documentation on Password
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication / Credential / Password.pm
CommitLineData
a90296d4 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Authentication::Credential::Password;
4
5use strict;
6use warnings;
7
8use Scalar::Util ();
9use Catalyst::Exception ();
10use Digest ();
11
54c8dc06 12sub new {
13 my ($class, $config, $app) = @_;
14
15 my $self = { %{$config} };
16 $self->{'password_field'} ||= 'password';
17 $self->{'password_type'} ||= 'clear';
18 $self->{'password_hash_type'} ||= 'SHA-1';
19
20 if (!grep /$$self{'password_type'}/, ('clear', 'hashed', 'salted_hash', 'crypted', 'self_check')) {
21 Catalyst::Exception->throw(__PACKAGE__ . " used with unsupported password type: " . $self->{'password_type'});
22 }
a90296d4 23
54c8dc06 24 bless $self, $class;
25}
26
27sub authenticate {
28 my ( $self, $c, $authstore, $authinfo ) = @_;
29
30 my $user_obj = $authstore->find_user($authinfo, $c);
31 if ($user_obj) {
32 if ($self->check_password($user_obj, $authinfo)) {
33 return $user_obj;
a93f1197 34 }
54c8dc06 35 } else {
36 $c->log->debug("Unable to locate user matching user info provided");
37 return;
38 }
39}
a93f1197 40
54c8dc06 41sub check_password {
42 my ( $self, $user, $authinfo ) = @_;
43
44 if ($self->{'password_type'} eq 'self_check') {
45 return $user->check_password($authinfo->{$self->{'password_field'}});
46 } else {
47 my $password = $authinfo->{$self->{'password_field'}};
48 my $storedpassword = $user->get($self->{'password_field'});
49
50 if ($self->{password_type} eq 'clear') {
51 return $password eq $storedpassword;
52 } elsif ($self->{'password_type'} eq 'crypted') {
53 return $storedpassword eq crypt( $password, $storedpassword );
54 } elsif ($self->{'password_type'} eq 'salted_hash') {
55 require Crypt::SaltedHash;
56 my $salt_len = $self->{'password_salt_len'} ? $self->{'password_salt_len'} : 0;
57 return Crypt::SaltedHash->validate( $storedpassword, $password,
58 $salt_len );
59 } elsif ($self->{'password_type'} eq 'hashed') {
60
61 my $d = Digest->new( $self->{'password_hash_type'} );
62 $d->add( $self->{'password_pre_salt'} || '' );
63 $d->add($password);
64 $d->add( $self->{'password_post_salt'} || '' );
65
66 my $computed = $d->clone()->digest;
67 my $b64computed = $d->clone()->b64digest;
68 return ( ( $computed eq $storedpassword )
69 || ( unpack( "H*", $computed ) eq $storedpassword )
70 || ( $b64computed eq $storedpassword)
71 || ( $b64computed.'=' eq $storedpassword) );
a93f1197 72 }
a90296d4 73 }
54c8dc06 74}
75
76## BACKWARDS COMPATIBILITY - all subs below here are deprecated
77## They are here for compatibility with older modules that use / inherit from C::P::A::Password
78## login()'s existance relies rather heavily on the fact that Credential::Password
79## is being used as a credential. This may not be the case. This is only here
80## for backward compatibility. It will go away in a future version
81## login should not be used in new applications.
a90296d4 82
54c8dc06 83sub login {
84 my ( $c, $user, $password, @rest ) = @_;
85
86 unless (
87 defined($user)
88 or
89 $user = $c->request->param("login")
90 || $c->request->param("user")
91 || $c->request->param("username")
92 ) {
93 $c->log->debug(
94 "Can't login a user without a user object or user ID param")
95 if $c->debug;
96 return;
97 }
98
99 unless (
100 defined($password)
101 or
102 $password = $c->request->param("password")
103 || $c->request->param("passwd")
104 || $c->request->param("pass")
105 ) {
106 $c->log->debug("Can't login a user without a password")
107 if $c->debug;
108 return;
109 }
110
a93f1197 111 unless ( Scalar::Util::blessed($user)
85d1d92d 112 and $user->isa("Catalyst::Plugin::Authentication::User") )
a93f1197 113 {
2f7d8b59 114 if ( my $user_obj = $c->get_user( $user, $password, @rest ) ) {
a93f1197 115 $user = $user_obj;
116 }
117 else {
118 $c->log->debug("User '$user' doesn't exist in the default store")
119 if $c->debug;
120 return;
121 }
122 }
a90296d4 123
124 if ( $c->_check_password( $user, $password ) ) {
125 $c->set_authenticated($user);
a93f1197 126 $c->log->debug("Successfully authenticated user '$user'.")
127 if $c->debug;
a90296d4 128 return 1;
129 }
130 else {
a93f1197 131 $c->log->debug(
85d1d92d 132 "Failed to authenticate user '$user'. Reason: 'Incorrect password'")
a93f1197 133 if $c->debug;
a90296d4 134 return;
135 }
54c8dc06 136
a90296d4 137}
138
54c8dc06 139## also deprecated. Here for compatibility with older credentials which do not inherit from C::P::A::Password
a90296d4 140sub _check_password {
141 my ( $c, $user, $password ) = @_;
54c8dc06 142
a90296d4 143 if ( $user->supports(qw/password clear/) ) {
144 return $user->password eq $password;
145 }
146 elsif ( $user->supports(qw/password crypted/) ) {
147 my $crypted = $user->crypted_password;
148 return $crypted eq crypt( $password, $crypted );
149 }
150 elsif ( $user->supports(qw/password hashed/) ) {
151
152 my $d = Digest->new( $user->hash_algorithm );
153 $d->add( $user->password_pre_salt || '' );
154 $d->add($password);
155 $d->add( $user->password_post_salt || '' );
156
fd5b31a0 157 my $stored = $user->hashed_password;
158 my $computed = $d->clone()->digest;
159 my $b64computed = $d->clone()->b64digest;
a90296d4 160
161 return ( ( $computed eq $stored )
fd5b31a0 162 || ( unpack( "H*", $computed ) eq $stored )
163 || ( $b64computed eq $stored)
164 || ( $b64computed.'=' eq $stored) );
a90296d4 165 }
166 elsif ( $user->supports(qw/password salted_hash/) ) {
167 require Crypt::SaltedHash;
168
169 my $salt_len =
170 $user->can("password_salt_len") ? $user->password_salt_len : 0;
171
172 return Crypt::SaltedHash->validate( $user->hashed_password, $password,
173 $salt_len );
174 }
175 elsif ( $user->supports(qw/password self_check/) ) {
176
177 # while somewhat silly, this is to prevent code duplication
178 return $user->check_password($password);
179
180 }
181 else {
182 Catalyst::Exception->throw(
183 "The user object $user does not support any "
184 . "known password authentication mechanism." );
185 }
186}
187
188__PACKAGE__;
189
190__END__
191
192=pod
193
194=head1 NAME
195
196Catalyst::Plugin::Authentication::Credential::Password - Authenticate a user
197with a password.
198
199=head1 SYNOPSIS
200
201 use Catalyst qw/
202 Authentication
a90296d4 203 /;
204
8d3ca09c 205 package MyApp::Controller::Auth;
206
a90296d4 207 sub login : Local {
208 my ( $self, $c ) = @_;
209
89505ffb 210 $c->authenticate( { username => $c->req->param('username'),
211 password => $c->req->param('password') });
a90296d4 212 }
213
214=head1 DESCRIPTION
215
89505ffb 216This authentication credential checker takes authentication information
217(most often a username) and a password, and attempts to validate the password
218provided against the user retrieved from the store.
a90296d4 219
89505ffb 220=head1 CONFIGURATION
a90296d4 221
89505ffb 222 # example
223 __PACKAGE__->config->{authentication} =
224 {
225 default_realm => 'members',
226 realms => {
227 members => {
228
229 credential => {
230 class => 'Password',
231 password_field => 'password',
232 password_type => 'hashed',
233 password_hash_type => 'SHA-1'
234 },
235 ...
a90296d4 236
a90296d4 237
89505ffb 238The password module is capable of working with several different password
239encryption/hashing algorithms. The one the module uses is determined by the
240credential configuration.
a90296d4 241
89505ffb 242=over 4
a90296d4 243
89505ffb 244=item class
a90296d4 245
89505ffb 246The classname used for Credential. This is part of
247L<Catalyst::Plugin::Authentication> and is the method by which
248Catalyst::Plugin::Authentication::Credential::Password is loaded as the
249credential validator. For this module to be used, this must be set to
250'Password'.
a90296d4 251
89505ffb 252=item password_field
a90296d4 253
89505ffb 254The field in the user object that contains the password. This will vary
255depending on the storage class used, but is most likely something like
256'password'. In fact, this is so common that if this is left out of the config,
257it defaults to 'password'. This field is obtained from the user object using
258the get() method. Essentially: $user->get('passwordfieldname');
a90296d4 259
89505ffb 260=item password_type
9b09fd1c 261
89505ffb 262This sets the password type. Often passwords are stored in crypted or hashed
263formats. In order for the password module to verify the plaintext password
264passed in, it must be told what format the password will be in when it is retreived
265from the user object. The supported options are:
9b09fd1c 266
89505ffb 267=over 8
9b09fd1c 268
89505ffb 269=item clear
9b09fd1c 270
89505ffb 271The password in user is in clear text and will be compared directly.
9b09fd1c 272
89505ffb 273=item self_check
9b09fd1c 274
89505ffb 275This option indicates that the password should be passed to the check_password()
276routine on the user object returned from the store.
9b09fd1c 277
89505ffb 278=item crypted
a90296d4 279
89505ffb 280The password in user is in UNIX crypt hashed format.
a90296d4 281
89505ffb 282=item salted_hash
a90296d4 283
89505ffb 284The password in user is in salted hash format, and will be validated
285using L<Crypt::SaltedHash>. If this password type is selected, you should
286also provide the B<password_salt_len> config element to define the salt length.
a90296d4 287
89505ffb 288=item hashed
a90296d4 289
89505ffb 290If the user object supports hashed passwords, they will be used in conjunction
291with L<Digest>. The following config elements affect the hashed configuration:
a90296d4 292
89505ffb 293=over 8
a90296d4 294
89505ffb 295=item password_hash_type
a90296d4 296
89505ffb 297The hash type used, passed directly to L<Digest/new>.
a90296d4 298
89505ffb 299=item password_pre_salt
a90296d4 300
89505ffb 301Any pre-salt data to be passed to L<Digest/add> before processing the password.
a90296d4 302
303=item password_post_salt
304
89505ffb 305Any post-salt data to be passed to L<Digest/add> after processing the password.
a90296d4 306
307=back
308
89505ffb 309=back
a90296d4 310
89505ffb 311=back
a90296d4 312
89505ffb 313=head1 USAGE
a90296d4 314
89505ffb 315The Password credential module is very simple to use. Once configured as indicated
316above, authenticating using this module is simply a matter of calling $c->authenticate()
317with an authinfo hashref that includes the B<password> element. The password element should
318contain the password supplied by the user to be authenticated, in clear text. The other
319information supplied in the auth hash is ignored by the Password module, and simply passed
320to the auth store to be used to retrieve the user. An example call follows:
a90296d4 321
89505ffb 322 if ($c->authenticate({ username => $username,
323 password => $password} )) {
324 # authentication successful
325 } else {
326 # authentication failed
327 }
a90296d4 328
89505ffb 329=head1 METHODS
a90296d4 330
89505ffb 331There are no publicly exported routines in the Password module (or indeed in
332most credential modules.) However, below is a description of the routines
333required by L<Catalyst::Plugin::Authentication> for all credential modules.
a90296d4 334
335=over 4
336
89505ffb 337=item new ( $config, $app )
a90296d4 338
89505ffb 339Instantiate a new Password object using the configuration hash provided in
340$config. A reference to the application is provided as the second argument.
341Note to credential module authors: new() is called during the application's
342plugin setup phase, which is before the application specific controllers are
343loaded. The practical upshot of this is that things like $c->model(...) will
344not function as expected.
a90296d4 345
89505ffb 346=item authenticate ( $authinfo, $c )
a90296d4 347
89505ffb 348Try to log a user in, receives a hashref containing authentication information
349as the first argument, and the current context as the second.
a90296d4 350
89505ffb 351=back