updated changes + version
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication / Credential / Password.pm
CommitLineData
a90296d4 1package Catalyst::Plugin::Authentication::Credential::Password;
45c7644b 2use base qw/Class::Accessor::Fast/;
a90296d4 3
4use strict;
5use warnings;
6
7use Scalar::Util ();
8use Catalyst::Exception ();
9use Digest ();
10
45c7644b 11BEGIN {
12 __PACKAGE__->mk_accessors(qw/_config/);
13}
14
54c8dc06 15sub new {
16 my ($class, $config, $app) = @_;
17
45c7644b 18 my $self = { _config => $config };
19 bless $self, $class;
20
21 $self->_config->{'password_field'} ||= 'password';
22 $self->_config->{'password_type'} ||= 'clear';
23 $self->_config->{'password_hash_type'} ||= 'SHA-1';
54c8dc06 24
45c7644b 25 my $passwordtype = $self->_config->{'password_type'};
52eebd31 26 if (!grep /$passwordtype/, ('none', 'clear', 'hashed', 'salted_hash', 'crypted', 'self_check')) {
45c7644b 27 Catalyst::Exception->throw(__PACKAGE__ . " used with unsupported password type: " . $self->_config->{'password_type'});
54c8dc06 28 }
45c7644b 29 return $self;
54c8dc06 30}
31
32sub authenticate {
33 my ( $self, $c, $authstore, $authinfo ) = @_;
34
45c7644b 35 ## because passwords may be in a hashed format, we have to make sure that we remove the
36 ## password_field before we pass it to the user routine, as some auth modules use
37 ## all data passed to them to find a matching user...
38 my $userfindauthinfo = {%{$authinfo}};
39 delete($userfindauthinfo->{$self->_config->{'password_field'}});
40
41 my $user_obj = $authstore->find_user($userfindauthinfo, $c);
649de93b 42 if (ref($user_obj)) {
54c8dc06 43 if ($self->check_password($user_obj, $authinfo)) {
44 return $user_obj;
a93f1197 45 }
54c8dc06 46 } else {
47 $c->log->debug("Unable to locate user matching user info provided");
48 return;
49 }
50}
a93f1197 51
54c8dc06 52sub check_password {
53 my ( $self, $user, $authinfo ) = @_;
54
45c7644b 55 if ($self->_config->{'password_type'} eq 'self_check') {
56 return $user->check_password($authinfo->{$self->_config->{'password_field'}});
54c8dc06 57 } else {
45c7644b 58 my $password = $authinfo->{$self->_config->{'password_field'}};
59 my $storedpassword = $user->get($self->_config->{'password_field'});
54c8dc06 60
17185597 61 if ($self->_config->{'password_type'} eq 'none') {
62 return 1;
63 } elsif ($self->_config->{'password_type'} eq 'clear') {
54c8dc06 64 return $password eq $storedpassword;
17185597 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
c5fbff80 91## login()'s existance relies rather heavily on the fact that only Credential::Password
54c8dc06 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
c5fbff80 255Those who have used L<Catalyst::Plugin::Authentication> prior to the 0.10 release
256should note that the password field and type information is no longer part
257of the store configuration and is now part of the Password credential configuration.
258
89505ffb 259=over 4
a90296d4 260
89505ffb 261=item class
a90296d4 262
89505ffb 263The classname used for Credential. This is part of
264L<Catalyst::Plugin::Authentication> and is the method by which
265Catalyst::Plugin::Authentication::Credential::Password is loaded as the
266credential validator. For this module to be used, this must be set to
267'Password'.
a90296d4 268
89505ffb 269=item password_field
a90296d4 270
89505ffb 271The field in the user object that contains the password. This will vary
272depending on the storage class used, but is most likely something like
273'password'. In fact, this is so common that if this is left out of the config,
274it defaults to 'password'. This field is obtained from the user object using
275the get() method. Essentially: $user->get('passwordfieldname');
a90296d4 276
89505ffb 277=item password_type
9b09fd1c 278
89505ffb 279This sets the password type. Often passwords are stored in crypted or hashed
280formats. In order for the password module to verify the plaintext password
281passed in, it must be told what format the password will be in when it is retreived
282from the user object. The supported options are:
9b09fd1c 283
89505ffb 284=over 8
9b09fd1c 285
17185597 286=item none
287
288No password check is done. An attempt is made to retrieve the user based on
289the information provided in the $c->authenticate() call. If a user is found,
290authentication is considered to be successful.
291
89505ffb 292=item clear
9b09fd1c 293
89505ffb 294The password in user is in clear text and will be compared directly.
9b09fd1c 295
89505ffb 296=item self_check
9b09fd1c 297
89505ffb 298This option indicates that the password should be passed to the check_password()
299routine on the user object returned from the store.
9b09fd1c 300
89505ffb 301=item crypted
a90296d4 302
89505ffb 303The password in user is in UNIX crypt hashed format.
a90296d4 304
89505ffb 305=item salted_hash
a90296d4 306
89505ffb 307The password in user is in salted hash format, and will be validated
308using L<Crypt::SaltedHash>. If this password type is selected, you should
309also provide the B<password_salt_len> config element to define the salt length.
a90296d4 310
89505ffb 311=item hashed
a90296d4 312
89505ffb 313If the user object supports hashed passwords, they will be used in conjunction
314with L<Digest>. The following config elements affect the hashed configuration:
a90296d4 315
89505ffb 316=over 8
a90296d4 317
89505ffb 318=item password_hash_type
a90296d4 319
89505ffb 320The hash type used, passed directly to L<Digest/new>.
a90296d4 321
89505ffb 322=item password_pre_salt
a90296d4 323
89505ffb 324Any pre-salt data to be passed to L<Digest/add> before processing the password.
a90296d4 325
326=item password_post_salt
327
89505ffb 328Any post-salt data to be passed to L<Digest/add> after processing the password.
a90296d4 329
330=back
331
89505ffb 332=back
a90296d4 333
89505ffb 334=back
a90296d4 335
89505ffb 336=head1 USAGE
a90296d4 337
c5fbff80 338The Password credential module is very simple to use. Once configured as
339indicated above, authenticating using this module is simply a matter of
340calling $c->authenticate() with an authinfo hashref that includes the
341B<password> element. The password element should contain the password supplied
342by the user to be authenticated, in clear text. The other information supplied
343in the auth hash is ignored by the Password module, and simply passed to the
344auth store to be used to retrieve the user. An example call follows:
a90296d4 345
89505ffb 346 if ($c->authenticate({ username => $username,
347 password => $password} )) {
348 # authentication successful
349 } else {
350 # authentication failed
351 }
a90296d4 352
89505ffb 353=head1 METHODS
a90296d4 354
89505ffb 355There are no publicly exported routines in the Password module (or indeed in
356most credential modules.) However, below is a description of the routines
357required by L<Catalyst::Plugin::Authentication> for all credential modules.
a90296d4 358
359=over 4
360
89505ffb 361=item new ( $config, $app )
a90296d4 362
89505ffb 363Instantiate a new Password object using the configuration hash provided in
364$config. A reference to the application is provided as the second argument.
365Note to credential module authors: new() is called during the application's
366plugin setup phase, which is before the application specific controllers are
367loaded. The practical upshot of this is that things like $c->model(...) will
368not function as expected.
a90296d4 369
89505ffb 370=item authenticate ( $authinfo, $c )
a90296d4 371
89505ffb 372Try to log a user in, receives a hashref containing authentication information
373as the first argument, and the current context as the second.
a90296d4 374
89505ffb 375=back