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