File Coverage

blib/lib/Catalyst/Plugin/Authentication/Credential/CHAP.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Authentication::Credential::CHAP;
2 1     1   24130 use base qw/ Catalyst::Plugin::Authentication::Credential::Password /;
  1         3  
  1         962  
3              
4             use strict;
5             use vars qw/ $VERSION /;
6             use warnings;
7              
8             $VERSION = '0.03';
9              
10             use Scalar::Util ();
11             use Catalyst::Exception ();
12             use Digest ();
13              
14             sub setup {
15              
16             my $c = shift;
17              
18             unless ($c->registered_plugins ('Session')) {
19             my $err = "The CHAP plugin requires Catalyst::Plugin::Session to be loaded!";
20             $c->log->fatal ($err);
21             Catalyst::Exception->throw ($err);
22             }
23              
24             # default values
25             $c->config->{authentication}->{chap}->{length} ||= 40;
26             $c->config->{authentication}->{chap}->{hash_algorithm} ||= 'MD5';
27             $c->config->{authentication}->{chap}->{chars} = ['a'..'z', 'A'..'Z', '0'..'9'];
28              
29             $c->NEXT::setup (@_);
30              
31             }
32              
33              
34             sub chap_init {
35             my ($c, $force) = shift;
36             $force ||= 0;
37             if ($c->session->{_chap_challenge_string}) {
38             return unless $force == 1;
39             }
40             $c->gen_challenge_string;
41             }
42              
43             sub _generate_challenge_string {
44             my $c = shift;
45             my $str;
46             my $length = $c->config->{authentication}->{chap}->{length};
47             foreach (1..$length) {
48             $str .= @{$c->config->{authentication}->{chap}->{chars}}[rand @{$c->config->{authentication}->{chap}->{chars}}];
49             }
50             return $str;
51             }
52              
53             sub gen_challenge_string {
54             my $c = shift;
55             my $str = $c->_generate_challenge_string;
56             $c->session->{_chap_challenge_string} = $str;
57              
58             }
59              
60             sub get_challenge_string {
61             my $c = shift;
62             return $c->session->{_chap_challenge_string};
63             }
64              
65             sub _check_password {
66             my ($c, $user, $password) = @_;
67              
68             my $user_password;
69              
70             if ($user->supports (qw/ password clear /)) {
71             $user_password = $user->password;
72             } elsif ($user->supports (qw/ password hashed /)) {
73             $user_password = $user->hashed_password;
74             } else {
75             Catalyst::Exception->throw("The authentication mechanism must be either clear or hashed.");
76             }
77              
78             my $d = Digest->new($c->config->{authentication}->{chap}->{hash_algorithm});
79             $d->add ($user_password);
80             $d->add ($c->get_challenge_string);
81             my $chap_password = $d->hexdigest;
82              
83             if ($c->debug) {
84             $c->log->debug ("User stored password: $user_password");
85             $c->log->debug ("User stored challenge: " . $c->get_challenge_string);
86             $c->log->debug ("CHAP Hash algorithm: " . $c->config->{authentication}->{chap}->{hash_algorithm});
87             $c->log->debug ("User chap password: $chap_password");
88             }
89              
90             return (($chap_password eq $password));
91             }
92              
93             __PACKAGE__;
94              
95             __END__
96              
97             =pod
98              
99             =head1 NAME
100              
101             Catalyst::Plugin::Authentication::Credential::CHAP - Authenticate a user
102             using a CHAP login system.
103              
104             =head1 SYNOPSIS
105              
106             use Catalyst qw/
107             Session
108             Session::Store::File
109             Session::State::Cookie
110             Authentication
111             Authentication::Store::Foo
112             Authentication::Credential::CHAP
113             /;
114              
115             __PACKAGE__->config->{authentication}->{chap} = {
116             hash_algorithm => 'SHA-1',
117             };
118              
119             sub begin : Private {
120             my ($self, $c) = @_;
121             $c->chap_init; # Generate a Challenge string and stores it in the session.
122             $c->stash("challenge_string", $c->get_challenge_string);
123            
124             }
125              
126             package MyApp::Controller::Auth;
127              
128             # *** NOTE ***
129             # if you place an action named 'login' in your application's root (as
130             # opposed to inside a controller) the following snippet will recurse,
131             # giving you lots of grief.
132             # never name actions in the root controller after plugin methods - use
133             # controllers and : Global instead.
134              
135             sub login : Local {
136             my ( $self, $c ) = @_;
137              
138             $c->login( $c->req->param('username'), $c->req->param('password') );
139             }
140              
141              
142             # Template.html
143             <form name="MyForm">
144             <input type="password" name="form_password" onclick="sendPassword();"/>
145             <input type="hidden" name="password" value="" />
146             <input type="hidden" name="challenge" value="[% challenge_string %]" />
147             </form>
148              
149             # Javascript (Client side)
150             function sendPassword() {
151             var password = document.forms['MyForm'].form_password.value
152             + document.forms['MyForm'].challenge.value;
153             document.forms['MyForm'].password.value = encode_sha1(password);
154             document.forms['MyForm'].form_password.value = '';
155             document.forms['MyForm'].challenge.value = '';
156             document.forms['MyForm'].submit();
157             }
158              
159              
160             =head1 DESCRIPTION
161              
162             This credential checker inherits from L<Catalyst::Plugin::Authentication::Credential::Password>.
163             It generates a challenge string that the user agent must concatenate to the password
164             before encoding it with a hash algorithm. When logging in, this plugin will compare the
165             sent password to the one stored, encoded with the same challenge string saved in the session data.
166             It is meant to allow you to securely send passwords over a clear HTTP connection.
167              
168              
169             =over 4
170              
171             =item clear text password
172              
173             If the user has a clear text password it will be compared directly. You just have to
174             concatenate the challenge string to the password and encode it with any of the hash methods
175             supported by the L<Digest> module.
176              
177             =item hashed password
178              
179             If the stored password is hashed, you will have to encode it in your client BEFORE concatenating
180             the challenge string to it, and then encode the whole string again before sending it to the server.
181              
182              
183             =item crypted password
184              
185             UNIX crypt hashed password are not supported. You must store your passwords either in clear
186             or hashed.
187              
188             =back
189              
190             =head1 REQUIREMENTS
191              
192             You must use Sessions.
193              
194             =head1 CONFIGURATION
195              
196             =over 4
197              
198             =item __PACKAGE__->config->{authentication}->{chap}->{length}
199              
200             The length of the challenge string. Default is 40.
201              
202              
203             =item __PACKAGE__->config->{authentication}->{chap}->{hash_algorithm}
204              
205             The hash method used to encode the password+challenge string, client-side. It can
206             be any method supported by the L<Digest> module, as long as you have a way to use the
207             same on the client. Default is MD5. More information and javascript functions can be found
208             at L<http://pajhome.org.uk/crypt/md5/>.
209              
210             =back
211              
212             =head1 METHODS
213              
214             =over 4
215              
216             =item login $username, $password
217              
218             Inherited from L<Catalyst::Plugin::Authentication::Credential::Password>.
219              
220             Try to log a user in.
221              
222             C<$username> can be a string (e.g. retrieved from a form) or an object.
223             If the object is a L<Catalyst::Plugin::Authentication::User> it will be used
224             as is. Otherwise C<< $c->get_user >> is used to retrieve it.
225              
226             C<$password> is a hash of the password and the challenge string, encoded
227             client side.
228              
229             If C<$username> or C<$password> are not provided, the query parameters
230             C<login>, C<user>, C<username> and C<password>, C<passwd>, C<pass> will
231             be tried instead.
232              
233             =item chap_init $force
234              
235             Generates a challenge string for the current session. You can put it in your root's
236             begin/end actions if needed, the challenge string won't change until the session ends
237             or you call this method with C<$force> set to 1.
238              
239             =item get_challenge_string
240              
241             Returns the current challenge string.
242              
243             =item setup
244              
245             =item gen_challenge_string
246              
247              
248             =back
249              
250             =head1 RELATED USAGE
251              
252             After the user is logged in, the user object for the current logged in user
253             can be retrieved from the context using the C<< $c->user >> method.
254              
255             The current user can be logged out again by calling the C<< $c->logout >>
256             method.
257              
258             =head1 SUPPORTING THIS PLUGIN
259              
260             See L<Catalyst::Plugin::Authentication::Credential::Password>.
261              
262             =head1 SEE ALSO
263              
264             L<Catalyst::Plugin::Authentication>, L<Catalyst::Plugin::Authentication::Credential::Password>,
265             L<Catalyst::Plugin::Session>.
266              
267             =head1 AUTHOR
268              
269             Renaud Drousies.
270              
271             =cut
272              
273