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