Bump versions and changelogging for dist
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Authentication / Credential / Password.pm
1 package Catalyst::Authentication::Credential::Password;
2
3 use strict;
4 use warnings;
5
6 use base qw/Class::Accessor::Fast/;
7
8 use Scalar::Util        ();
9 use Catalyst::Exception ();
10 use Digest              ();
11
12 BEGIN {
13     __PACKAGE__->mk_accessors(qw/_config realm/);
14 }
15
16 sub new {
17     my ($class, $config, $app, $realm) = @_;
18     
19     my $self = { _config => $config };
20     bless $self, $class;
21     
22     $self->realm($realm);
23     
24     $self->_config->{'password_field'} ||= 'password';
25     $self->_config->{'password_type'}  ||= 'clear';
26     $self->_config->{'password_hash_type'} ||= 'SHA-1';
27     
28     my $passwordtype = $self->_config->{'password_type'};
29     if (!grep /$passwordtype/, ('none', 'clear', 'hashed', 'salted_hash', 'crypted', 'self_check')) {
30         Catalyst::Exception->throw(__PACKAGE__ . " used with unsupported password type: " . $self->_config->{'password_type'});
31     }
32     return $self;
33 }
34
35 sub authenticate {
36     my ( $self, $c, $realm, $authinfo ) = @_;
37
38     ## because passwords may be in a hashed format, we have to make sure that we remove the 
39     ## password_field before we pass it to the user routine, as some auth modules use 
40     ## all data passed to them to find a matching user... 
41     my $userfindauthinfo = {%{$authinfo}};
42     delete($userfindauthinfo->{$self->_config->{'password_field'}});
43     
44     my $user_obj = $realm->find_user($userfindauthinfo, $c);
45     if (ref($user_obj)) {
46         if ($self->check_password($user_obj, $authinfo)) {
47             return $user_obj;
48         }
49     } else {
50         $c->log->debug("Unable to locate user matching user info provided") if $c->debug;
51         return;
52     }
53 }
54
55 sub check_password {
56     my ( $self, $user, $authinfo ) = @_;
57     
58     if ($self->_config->{'password_type'} eq 'self_check') {
59         return $user->check_password($authinfo->{$self->_config->{'password_field'}});
60     } else {
61         my $password = $authinfo->{$self->_config->{'password_field'}};
62         my $storedpassword = $user->get($self->_config->{'password_field'});
63         
64         if ($self->_config->{'password_type'} eq 'none') {
65             return 1;
66         } elsif ($self->_config->{'password_type'} eq 'clear') {
67             return $password eq $storedpassword;
68         } elsif ($self->_config->{'password_type'} eq 'crypted') {            
69             return $storedpassword eq crypt( $password, $storedpassword );
70         } elsif ($self->_config->{'password_type'} eq 'salted_hash') {
71             require Crypt::SaltedHash;
72             my $salt_len = $self->_config->{'password_salt_len'} ? $self->_config->{'password_salt_len'} : 0;
73             return Crypt::SaltedHash->validate( $storedpassword, $password,
74                 $salt_len );
75         } elsif ($self->_config->{'password_type'} eq 'hashed') {
76
77              my $d = Digest->new( $self->_config->{'password_hash_type'} );
78              $d->add( $self->_config->{'password_pre_salt'} || '' );
79              $d->add($password);
80              $d->add( $self->_config->{'password_post_salt'} || '' );
81
82              my $computed    = $d->clone()->digest;
83              my $b64computed = $d->clone()->b64digest;
84              return ( ( $computed eq $storedpassword )
85                    || ( unpack( "H*", $computed ) eq $storedpassword )
86                    || ( $b64computed eq $storedpassword)
87                    || ( $b64computed.'=' eq $storedpassword) );
88         }
89     }
90 }
91
92 __PACKAGE__;
93
94 __END__
95
96 =pod
97
98 =head1 NAME
99
100 Catalyst::Authentication::Credential::Password - Authenticate a user
101 with a password.
102
103 =head1 SYNOPSIS
104
105     use Catalyst qw/
106       Authentication
107       /;
108
109     package MyApp::Controller::Auth;
110
111     sub login : Local {
112         my ( $self, $c ) = @_;
113
114         $c->authenticate( { username => $c->req->param('username'),
115                             password => $c->req->param('password') });
116     }
117
118 =head1 DESCRIPTION
119
120 This authentication credential checker takes authentication information
121 (most often a username) and a password, and attempts to validate the password
122 provided against the user retrieved from the store.
123
124 =head1 CONFIGURATION
125
126     # example
127     __PACKAGE__->config->{'Plugin::Authentication'} = 
128                 {  
129                     default_realm => 'members',
130                     realms => {
131                         members => {
132                             
133                             credential => {
134                                 class => 'Password',
135                                 password_field => 'password',
136                                 password_type => 'hashed',
137                                 password_hash_type => 'SHA-1'                                
138                             },    
139                             ...
140
141
142 The password module is capable of working with several different password
143 encryption/hashing algorithms. The one the module uses is determined by the
144 credential configuration.
145
146 Those who have used L<Catalyst::Plugin::Authentication> prior to the 0.10 release
147 should note that the password field and type information is no longer part
148 of the store configuration and is now part of the Password credential configuration.
149
150 =over 4 
151
152 =item class 
153
154 The classname used for Credential. This is part of
155 L<Catalyst::Plugin::Authentication> and is the method by which
156 Catalyst::Authentication::Credential::Password is loaded as the
157 credential validator. For this module to be used, this must be set to
158 'Password'.
159
160 =item password_field
161
162 The field in the user object that contains the password. This will vary
163 depending on the storage class used, but is most likely something like
164 'password'. In fact, this is so common that if this is left out of the config,
165 it defaults to 'password'. This field is obtained from the user object using
166 the get() method. Essentially: $user->get('passwordfieldname'); 
167 B<NOTE> If the password_field is something other than 'password', you must 
168 be sure to use that same field name when calling $c->authenticate(). 
169
170 =item password_type 
171
172 This sets the password type.  Often passwords are stored in crypted or hashed
173 formats.  In order for the password module to verify the plaintext password 
174 passed in, it must be told what format the password will be in when it is retreived
175 from the user object. The supported options are:
176
177 =over 8
178
179 =item none
180
181 No password check is done. An attempt is made to retrieve the user based on
182 the information provided in the $c->authenticate() call. If a user is found, 
183 authentication is considered to be successful.
184
185 =item clear
186
187 The password in user is in clear text and will be compared directly.
188
189 =item self_check
190
191 This option indicates that the password should be passed to the check_password()
192 routine on the user object returned from the store.  
193
194 =item crypted
195
196 The password in user is in UNIX crypt hashed format.  
197
198 =item salted_hash
199
200 The password in user is in salted hash format, and will be validated
201 using L<Crypt::SaltedHash>.  If this password type is selected, you should
202 also provide the B<password_salt_len> config element to define the salt length.
203
204 =item hashed
205
206 If the user object supports hashed passwords, they will be used in conjunction
207 with L<Digest>. The following config elements affect the hashed configuration:
208
209 =over 8
210
211 =item password_hash_type 
212
213 The hash type used, passed directly to L<Digest/new>.  
214
215 =item password_pre_salt 
216
217 Any pre-salt data to be passed to L<Digest/add> before processing the password.
218
219 =item password_post_salt
220
221 Any post-salt data to be passed to L<Digest/add> after processing the password.
222
223 =back
224
225 =back
226
227 =back
228
229 =head1 USAGE
230
231 The Password credential module is very simple to use. Once configured as
232 indicated above, authenticating using this module is simply a matter of
233 calling $c->authenticate() with an authinfo hashref that includes the
234 B<password> element. The password element should contain the password supplied
235 by the user to be authenticated, in clear text. The other information supplied
236 in the auth hash is ignored by the Password module, and simply passed to the
237 auth store to be used to retrieve the user. An example call follows:
238
239     if ($c->authenticate({ username => $username,
240                            password => $password} )) {
241         # authentication successful
242     } else {
243         # authentication failed
244     }
245
246 =head1 METHODS
247
248 There are no publicly exported routines in the Password module (or indeed in
249 most credential modules.)  However, below is a description of the routines 
250 required by L<Catalyst::Plugin::Authentication> for all credential modules.
251
252 =head2 new( $config, $app, $realm )
253
254 Instantiate a new Password object using the configuration hash provided in
255 $config. A reference to the application is provided as the second argument.
256 Note to credential module authors: new() is called during the application's
257 plugin setup phase, which is before the application specific controllers are
258 loaded. The practical upshot of this is that things like $c->model(...) will
259 not function as expected.
260
261 =head2 authenticate( $authinfo, $c )
262
263 Try to log a user in, receives a hashref containing authentication information
264 as the first argument, and the current context as the second.
265
266 =head2 check_password( )
267
268 =cut