Commit | Line | Data |
a90296d4 |
1 | #!/usr/bin/perl |
2 | |
3 | package Catalyst::Plugin::Authentication::Credential::Password; |
4 | |
5 | use strict; |
6 | use warnings; |
7 | |
8 | use Scalar::Util (); |
9 | use Catalyst::Exception (); |
10 | use Digest (); |
11 | |
54c8dc06 |
12 | sub 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 | |
27 | sub authenticate { |
28 | my ( $self, $c, $authstore, $authinfo ) = @_; |
29 | |
30 | my $user_obj = $authstore->find_user($authinfo, $c); |
649de93b |
31 | if (ref($user_obj)) { |
54c8dc06 |
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 |
41 | sub 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 |
83 | sub 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 |
140 | sub _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 | |
196 | Catalyst::Plugin::Authentication::Credential::Password - Authenticate a user |
197 | with 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 |
216 | This authentication credential checker takes authentication information |
217 | (most often a username) and a password, and attempts to validate the password |
218 | provided 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 |
238 | The password module is capable of working with several different password |
239 | encryption/hashing algorithms. The one the module uses is determined by the |
240 | credential configuration. |
a90296d4 |
241 | |
89505ffb |
242 | =over 4 |
a90296d4 |
243 | |
89505ffb |
244 | =item class |
a90296d4 |
245 | |
89505ffb |
246 | The classname used for Credential. This is part of |
247 | L<Catalyst::Plugin::Authentication> and is the method by which |
248 | Catalyst::Plugin::Authentication::Credential::Password is loaded as the |
249 | credential validator. For this module to be used, this must be set to |
250 | 'Password'. |
a90296d4 |
251 | |
89505ffb |
252 | =item password_field |
a90296d4 |
253 | |
89505ffb |
254 | The field in the user object that contains the password. This will vary |
255 | depending 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, |
257 | it defaults to 'password'. This field is obtained from the user object using |
258 | the get() method. Essentially: $user->get('passwordfieldname'); |
a90296d4 |
259 | |
89505ffb |
260 | =item password_type |
9b09fd1c |
261 | |
89505ffb |
262 | This sets the password type. Often passwords are stored in crypted or hashed |
263 | formats. In order for the password module to verify the plaintext password |
264 | passed in, it must be told what format the password will be in when it is retreived |
265 | from the user object. The supported options are: |
9b09fd1c |
266 | |
89505ffb |
267 | =over 8 |
9b09fd1c |
268 | |
89505ffb |
269 | =item clear |
9b09fd1c |
270 | |
89505ffb |
271 | The password in user is in clear text and will be compared directly. |
9b09fd1c |
272 | |
89505ffb |
273 | =item self_check |
9b09fd1c |
274 | |
89505ffb |
275 | This option indicates that the password should be passed to the check_password() |
276 | routine on the user object returned from the store. |
9b09fd1c |
277 | |
89505ffb |
278 | =item crypted |
a90296d4 |
279 | |
89505ffb |
280 | The password in user is in UNIX crypt hashed format. |
a90296d4 |
281 | |
89505ffb |
282 | =item salted_hash |
a90296d4 |
283 | |
89505ffb |
284 | The password in user is in salted hash format, and will be validated |
285 | using L<Crypt::SaltedHash>. If this password type is selected, you should |
286 | also provide the B<password_salt_len> config element to define the salt length. |
a90296d4 |
287 | |
89505ffb |
288 | =item hashed |
a90296d4 |
289 | |
89505ffb |
290 | If the user object supports hashed passwords, they will be used in conjunction |
291 | with 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 |
297 | The hash type used, passed directly to L<Digest/new>. |
a90296d4 |
298 | |
89505ffb |
299 | =item password_pre_salt |
a90296d4 |
300 | |
89505ffb |
301 | Any pre-salt data to be passed to L<Digest/add> before processing the password. |
a90296d4 |
302 | |
303 | =item password_post_salt |
304 | |
89505ffb |
305 | Any 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 |
315 | The Password credential module is very simple to use. Once configured as indicated |
316 | above, authenticating using this module is simply a matter of calling $c->authenticate() |
317 | with an authinfo hashref that includes the B<password> element. The password element should |
318 | contain the password supplied by the user to be authenticated, in clear text. The other |
319 | information supplied in the auth hash is ignored by the Password module, and simply passed |
320 | to 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 |
331 | There are no publicly exported routines in the Password module (or indeed in |
332 | most credential modules.) However, below is a description of the routines |
333 | required by L<Catalyst::Plugin::Authentication> for all credential modules. |
a90296d4 |
334 | |
335 | =over 4 |
336 | |
89505ffb |
337 | =item new ( $config, $app ) |
a90296d4 |
338 | |
89505ffb |
339 | Instantiate a new Password object using the configuration hash provided in |
340 | $config. A reference to the application is provided as the second argument. |
341 | Note to credential module authors: new() is called during the application's |
342 | plugin setup phase, which is before the application specific controllers are |
343 | loaded. The practical upshot of this is that things like $c->model(...) will |
344 | not function as expected. |
a90296d4 |
345 | |
89505ffb |
346 | =item authenticate ( $authinfo, $c ) |
a90296d4 |
347 | |
89505ffb |
348 | Try to log a user in, receives a hashref containing authentication information |
349 | as the first argument, and the current context as the second. |
a90296d4 |
350 | |
89505ffb |
351 | =back |