Added overview - to clarify how the system really works.
[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
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
89505ffb 286=item clear
9b09fd1c 287
89505ffb 288The password in user is in clear text and will be compared directly.
9b09fd1c 289
89505ffb 290=item self_check
9b09fd1c 291
89505ffb 292This option indicates that the password should be passed to the check_password()
293routine on the user object returned from the store.
9b09fd1c 294
89505ffb 295=item crypted
a90296d4 296
89505ffb 297The password in user is in UNIX crypt hashed format.
a90296d4 298
89505ffb 299=item salted_hash
a90296d4 300
89505ffb 301The password in user is in salted hash format, and will be validated
302using L<Crypt::SaltedHash>. If this password type is selected, you should
303also provide the B<password_salt_len> config element to define the salt length.
a90296d4 304
89505ffb 305=item hashed
a90296d4 306
89505ffb 307If the user object supports hashed passwords, they will be used in conjunction
308with L<Digest>. The following config elements affect the hashed configuration:
a90296d4 309
89505ffb 310=over 8
a90296d4 311
89505ffb 312=item password_hash_type
a90296d4 313
89505ffb 314The hash type used, passed directly to L<Digest/new>.
a90296d4 315
89505ffb 316=item password_pre_salt
a90296d4 317
89505ffb 318Any pre-salt data to be passed to L<Digest/add> before processing the password.
a90296d4 319
320=item password_post_salt
321
89505ffb 322Any post-salt data to be passed to L<Digest/add> after processing the password.
a90296d4 323
324=back
325
89505ffb 326=back
a90296d4 327
89505ffb 328=back
a90296d4 329
89505ffb 330=head1 USAGE
a90296d4 331
c5fbff80 332The Password credential module is very simple to use. Once configured as
333indicated above, authenticating using this module is simply a matter of
334calling $c->authenticate() with an authinfo hashref that includes the
335B<password> element. The password element should contain the password supplied
336by the user to be authenticated, in clear text. The other information supplied
337in the auth hash is ignored by the Password module, and simply passed to the
338auth store to be used to retrieve the user. An example call follows:
a90296d4 339
89505ffb 340 if ($c->authenticate({ username => $username,
341 password => $password} )) {
342 # authentication successful
343 } else {
344 # authentication failed
345 }
a90296d4 346
89505ffb 347=head1 METHODS
a90296d4 348
89505ffb 349There are no publicly exported routines in the Password module (or indeed in
350most credential modules.) However, below is a description of the routines
351required by L<Catalyst::Plugin::Authentication> for all credential modules.
a90296d4 352
353=over 4
354
89505ffb 355=item new ( $config, $app )
a90296d4 356
89505ffb 357Instantiate a new Password object using the configuration hash provided in
358$config. A reference to the application is provided as the second argument.
359Note to credential module authors: new() is called during the application's
360plugin setup phase, which is before the application specific controllers are
361loaded. The practical upshot of this is that things like $c->model(...) will
362not function as expected.
a90296d4 363
89505ffb 364=item authenticate ( $authinfo, $c )
a90296d4 365
89505ffb 366Try to log a user in, receives a hashref containing authentication information
367as the first argument, and the current context as the second.
a90296d4 368
89505ffb 369=back