File Coverage

blib/lib/CGI/Application/Plugin/Authentication.pm
Criterion Covered Total %
statement 381 413 92.2
branch 207 228 90.7
condition 83 109 76.1
subroutine 52 53 98.1
pod 26 28 92.8
total 749 831 90.1


)
line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::Authentication;
2              
3 31     31   2672634 use 5.006;
  31         134  
  31         1384  
4 31     31   191 use strict;
  31         68  
  31         2227  
5             our $VERSION = '0.20';
6              
7             our %__CONFIG;
8              
9 31     31   1042 use Class::ISA ();
  31         3552  
  31         559  
10 31     31   177 use Scalar::Util ();
  31         59  
  31         625  
11 31     31   26930 use UNIVERSAL::require;
  31         60811  
  31         527  
12 31     31   889 use Carp;
  31         74  
  31         1759  
13 31     31   65289 use CGI ();
  31         318604  
  31         1761  
14              
15             sub import {
16 33     33   14997 my $pkg = shift;
17 33         532 my $callpkg = caller;
18             {
19 31     31   251 no strict qw(refs);
  31         60  
  31         4114  
  33         786  
20 33         113 *{$callpkg.'::authen'} = \&CGI::Application::Plugin::_::Authentication::authen;
  33         288  
21             }
22 33 100       311 if ( ! UNIVERSAL::isa($callpkg, 'CGI::Application') ) {
23 2         23 warn "Calling package is not a CGI::Application module so not setting up the prerun hook. If you are using \@ISA instead of 'use base', make sure it is in a BEGIN { } block, and make sure these statements appear before the plugin is loaded";
24             } else {
25 31         469 $callpkg->add_callback( prerun => \&prerun_callback );
26             }
27             }
28              
29 31     31   39863 use Attribute::Handlers;
  31         225662  
  31         211  
30             my %RUNMODES;
31              
32             sub CGI::Application::RequireAuthentication : ATTR(CODE) {
33 1     1 0 1910 my ( $package, $symbol, $referent, $attr, $data, $phase ) = @_;
34 1   50     14 $RUNMODES{$referent} = $data || 1;
35 31     31   3488 }
  31         68  
  31         151  
36             sub CGI::Application::Authen : ATTR(CODE) {
37 1     1 0 262 my ( $package, $symbol, $referent, $attr, $data, $phase ) = @_;
38 1   50     9 $RUNMODES{$referent} = $data || 1;
39 31     31   17829 }
  31         52  
  31         138  
40              
41              
42             =head1 NAME
43              
44             CGI::Application::Plugin::Authentication - Authentication framework for CGI::Application
45              
46             =head1 VERSION
47              
48             This document describes CGI::Application::Plugin::Authentication version 0.20
49              
50             =head1 SYNOPSIS
51              
52             package MyCGIApp;
53              
54             use base qw(CGI::Application); # make sure this occurs before you load the plugin
55              
56             use CGI::Application::Plugin::Authentication;
57              
58             MyCGIApp->authen->config(
59             DRIVER => [ 'Generic', { user1 => '123' } ],
60             );
61             MyCGIApp->authen->protected_runmodes('myrunmode');
62              
63             sub myrunmode {
64             my $self = shift;
65              
66             # The user should be logged in if we got here
67             my $username = $self->authen->username;
68              
69             }
70              
71             =head1 DESCRIPTION
72              
73             CGI::Application::Plugin::Authentication adds the ability to authenticate users
74             in your L modules. It imports one method called 'authen' into your
75             CGI::Application module. Through the authen method you can call all the methods of
76             the CGI::Application::Plugin::Authentication plugin.
77              
78             There are two main decisions that you need to make when using this module. How will
79             the usernames and password be verified (i.e. from a database, LDAP, etc...), and how
80             can we keep the knowledge that a user has already logged in persistent, so that they
81             will not have to enter their credentials again on the next request (i.e. how do we 'Store'
82             the authentication information across requests).
83              
84             =head2 Choosing a Driver
85              
86             There are three drivers that are included with the distribution. Also, there
87             is built in support for all of the Authen::Simple modules (search CPAN for
88             Authen::Simple for more information). This should be enough to cover
89             everyone's needs.
90              
91             If you need to authenticate against a source that is not provided, you can use
92             the Generic driver which will accept either a hash of username/password pairs,
93             or an array of arrays of credentials, or a subroutine reference that can verify
94             the credentials. So through the Generic driver you should be able to write
95             your own verification system. There is also a Dummy driver, which blindly
96             accepts any credentials (useful for testing). See the
97             L,
98             L and,
99             L docs for more
100             information on how to use these drivers. And see the L suite
101             of modules for information on those drivers.
102              
103             =head2 Choosing a Store
104              
105             The Store modules keep information about the authentication status of the user persistent
106             across multiple requests. The information that is stored in the store include the username,
107             and the expiry time of the login. There are two Store modules included with this distribution.
108             A Session based store, and a Cookie based store. If your application is already using
109             Sessions (through the L module), then I would recommend
110             that you use the Session store for authentication. If you are not using the Session
111             plugin, then you can use the Cookie store. The Cookie store keeps all the authentication
112             in a cookie, which contains a checksum to ensure that users can not change the information.
113              
114             If you do not specify which Store module you wish to use, the plugin will try to determine
115             the best one for you.
116              
117             =head2 Login page
118              
119             The Authentication plugin comes with a default login page that can be used if you do not
120             want to create a custom login page. This login form will automatically be used if you
121             do not provide either a LOGIN_URL or LOGIN_RUNMODE parameter in the configuration.
122             If you plan to create your own login page, I would recommend that you start with the HTML
123             code for the default login page, so that your login page will contain the correct form
124             fields and hidden fields.
125              
126             =head2 Ticket based authentication
127              
128             This Authentication plugin can handle ticket based authentication systems as well. All that
129             is required of you is to write a Store module that can understand the contents of the ticket.
130             The Authentication plugin will require at least the 'username' to be retrieved from the
131             ticket. A Ticket based authentication scheme will not need a Driver module at all, since the
132             actual verification of credentials is done by an external authentication system, possibly
133             even on a different host. You will need to specify the location of the login page using
134             the LOGIN_URL configuration variable, and unauthenticated users will automatically
135             be redirected to your ticket authentication login page.
136              
137              
138             =head1 EXPORTED METHODS
139              
140             =head2 authen
141              
142             This is the only method exported from this module. Everything is controlled
143             through this method call, which will return a CGI::Application::Plugin::Authentication
144             object, or just the class name if called as a class method. When using
145             the plugin, you will always first call $self->authen or __PACKAGE__->authen and
146             then the method you wish to invoke. For example:
147              
148             __PACKAGE__->authen->config(
149             LOGIN_RUNMODE => 'login',
150             );
151              
152             - or -
153              
154             $self->authen->protected_runmodes(qw(one two));
155              
156             =cut
157              
158              
159             { package # Hide from PAUSE
160             CGI::Application::Plugin::_::Authentication;
161              
162             ##############################################
163             ###
164             ### authen
165             ###
166             ##############################################
167             #
168             # Return an authen object that can be used
169             # for managing authentication.
170             #
171             # This will return a class name if called
172             # as a class name, and a singleton object
173             # if called as an object method
174             #
175             sub authen {
176 2121     2121   10554017 my $cgiapp = shift;
177              
178 2121 100       7410 if (ref($cgiapp)) {
179 2091         7636 return CGI::Application::Plugin::Authentication->instance($cgiapp);
180             } else {
181 30         617 return 'CGI::Application::Plugin::Authentication';
182             }
183             }
184              
185             }
186              
187             package CGI::Application::Plugin::Authentication;
188              
189              
190              
191             =head1 METHODS
192              
193             =head2 config
194              
195             This method is used to configure the CGI::Application::Plugin::Authentication
196             module. It can be called as an object method, or as a class method. Calling this function,
197             will not itself generate cookies or session ids.
198              
199             The following parameters are accepted:
200              
201             =over 4
202              
203             =item DRIVER
204              
205             Here you can choose which authentication module(s) you want to use to perform the authentication.
206             For simplicity, you can leave off the CGI::Application::Plugin::Authentication::Driver:: part
207             when specifying the DRIVER name If this module requires extra parameters, you
208             can pass an array reference that contains as the first parameter the name of the module,
209             and the rest of the values in the array will be considered options for the driver. You can provide
210             multiple drivers which will be used, in order, to check the credentials until
211             a valid response is received.
212              
213             DRIVER => 'Dummy' # let anyone in regardless of the password
214              
215             - or -
216              
217             DRIVER => [ 'DBI',
218             DBH => $self->dbh,
219             TABLE => 'user',
220             CONSTRAINTS => {
221             'user.name' => '__CREDENTIAL_1__',
222             'MD5:user.password' => '__CREDENTIAL_2__'
223             },
224             ],
225              
226             - or -
227              
228             DRIVER => [
229             [ 'Generic', { user1 => '123' } ],
230             [ 'Generic', sub { my ($u, $p) = @_; is_prime($p) ? 1 : 0 } ]
231             ],
232              
233             - or -
234              
235             DRIVER => [ 'Authen::Simple::LDAP',
236             host => 'ldap.company.com',
237             basedn => 'ou=People,dc=company,dc=net'
238             ],
239              
240              
241             =item STORE
242              
243             Here you can choose how we store the authenticated information after a user has successfully
244             logged in. We need to store the username so that on the next request we can tell the user
245             has already logged in, and we do not have to present them with another login form. If you
246             do not provide the STORE option, then the plugin will look to see if you are using the
247             L module and based on that info use either the Session
248             module, or fall back on the Cookie module. If the module requires extra parameters, you
249             can pass an array reference that contains as the first parameter the name of the module,
250             and the rest of the array should contain key value pairs of options for this module.
251             These storage modules generally live under the CGI::Application::Plugin::Authentication::Store::
252             name-space, and this part of the package name can be left off when specifying the STORE
253             parameter.
254              
255             STORE => 'Session'
256              
257             - or -
258              
259             STORE => ['Cookie',
260             NAME => 'MYAuthCookie',
261             SECRET => 'FortyTwo',
262             EXPIRY => '1d',
263             ]
264              
265              
266             =item POST_LOGIN_RUNMODE
267              
268             Here you can specify a runmode that the user will be redirected to if they successfully login.
269              
270             POST_LOGIN_RUNMODE => 'welcome'
271              
272             =item POST_LOGIN_URL
273              
274             Here you can specify a URL that the user will be redirected to if they successfully login.
275             If both POST_LOGIN_URL and POST_LOGIN_RUNMODE are specified, then the latter
276             will take precedence.
277              
278             POST_LOGIN_URL => 'http://example.com/start.cgi'
279              
280             =item POST_LOGIN_CALLBACK
281              
282             A code reference that is executed after login processing but before POST_LOGIN_RUNMODE or
283             redirecting to POST_LOGIN_URL. This is normally a method in your CGI::Application application
284             and as such the CGI::Application object is passed as a parameter.
285              
286             POST_LOGIN_CALLBACK => \&update_login_date
287              
288             and later in your code:
289              
290             sub update_login_date {
291             my $self = shift;
292              
293             return unless($self->authen->is_authenticated);
294              
295             ...
296             }
297              
298              
299             =item LOGIN_RUNMODE
300              
301             Here you can specify a runmode that the user will be redirected to if they need to login.
302              
303             LOGIN_RUNMODE => 'login'
304              
305             =item LOGIN_URL
306              
307             If your login page is external to this module, then you can use this option to specify a
308             URL that the user will be redirected to when they need to login. If both
309             LOGIN_URL and LOGIN_RUNMODE are specified, then the latter will take precedence.
310              
311             LOGIN_URL => 'http://example.com/login.cgi'
312              
313             =item LOGOUT_RUNMODE
314              
315             Here you can specify a runmode that the user will be redirected to if they ask to logout.
316              
317             LOGOUT_RUNMODE => 'logout'
318              
319             =item LOGOUT_URL
320              
321             If your logout page is external to this module, then you can use this option to specify a
322             URL that the user will be redirected to when they ask to logout. If both
323             LOGOUT_URL and LOGOUT_RUNMODE are specified, then the latter will take precedence.
324              
325             LOGIN_URL => 'http://example.com/logout.html'
326              
327             =item DETAINT_URL_REGEXP
328              
329             This is a regular expression used to detaint URLs used in the login form. By default it will be set to
330              
331             ^([\w\_\%\?\&\;\-\/\@\.\+\$\=\#\:\!\*\"\'\(\)\,]+)$
332              
333             This regular expression is based upon the document http://www.w3.org/Addressing/URL/url-spec.txt. You could
334             set it to a more specific regular expression to limit the domains to which users could be directed.
335              
336             =item DETAINT_USERNAME_REGEXP
337              
338             This is a regular expression used to detaint the username parameter used in the login form. By default it will be set to
339              
340             ^([\w\_]+)$
341              
342             =item CREDENTIALS
343              
344             Set this to the list of form fields where the user will type in their username and password.
345             By default this is set to ['authen_username', 'authen_password']. The form field names should
346             be set to a value that you are not likely to use in any other forms. This is important
347             because this plugin will automatically look for query parameters that match these values on
348             every request to see if a user is trying to log in. So if you use the same parameter names
349             on a user management page, you may inadvertently perform a login when that was not intended.
350             Most of the Driver modules will return the first CREDENTIAL as the username, so make sure
351             that you list the username field first. This option can be ignored if you use the built in
352             login box
353              
354             CREDENTIALS => 'authen_password'
355              
356             - or -
357              
358             CREDENTIALS => [ 'authen_username', 'authen_domain', 'authen_password' ]
359              
360              
361             =item LOGIN_SESSION_TIMEOUT
362              
363              
364             This option can be used to tell the system when to force the user to re-authenticate. There are
365             a few different possibilities that can all be used concurrently:
366              
367             =over 4
368              
369             =item IDLE_FOR
370              
371             If this value is set, a re-authentication will be forced if the user was idle for more then x amount of time.
372              
373             =item EVERY
374              
375             If this value is set, a re-authentication will be forced every x amount of time.
376              
377             =item CUSTOM
378              
379             This value can be set to a subroutine reference that returns true if the session should be timed out,
380             and false if it is still active. This can allow you to be very selective about how the timeout system
381             works. The authen object will be passed in as the only parameter.
382              
383             =back
384              
385             Time values are specified in seconds. You can also specify the time by using a number with the
386             following suffixes (m h d w), which represent minutes, hours, days and weeks. The default
387             is 0 which means the login will never timeout.
388              
389             Note that the login is also dependent on the type of STORE that is used. If the Session store is used,
390             and the session expires, then the login will also automatically expire. The same goes for the Cookie
391             store.
392              
393             For backwards compatibility, if you set LOGIN_SESSION_TIMEOUT to a time value instead of a hashref,
394             it will be treated as an IDLE_FOR time out.
395              
396             # force re-authentication if idle for more than 15 minutes
397             LOGIN_SESSION_TIMEOUT => '15m'
398              
399             # Everyone must re-authentication if idle for more than 30 minutes
400             # also, everyone must re-authentication at least once a day
401             # and root must re-authentication if idle for more than 5 minutes
402             LOGIN_SESSION_TIMEOUT => {
403             IDLE_FOR => '30m',
404             EVERY => '1d',
405             CUSTOM => sub {
406             my $authen = shift;
407             return ($authen->username eq 'root' && (time() - $authen->last_access) > 300) ? 1 : 0;
408             }
409             }
410              
411             =item RENDER_LOGIN
412              
413             This value can be set to a subroutine reference that returns the HTML of a login
414             form. The subroutine reference overrides the default call to login_box.
415             The subroutine is normally a method in your CGI::Application application and as such the
416             CGI::Application object is passed as the first parameter.
417              
418             RENDER_LOGIN => \&login_form
419              
420             and later in your code:
421              
422             sub login_form {
423             my $self = shift;
424              
425             ...
426             return $html
427             }
428              
429             =item LOGIN_FORM
430              
431             You can set this option to customize the login form that is created when a user
432             needs to be authenticated. If you wish to replace the entire login form with a
433             completely custom version, then just set LOGIN_RUNMODE to point to your custom
434             runmode.
435              
436             All of the parameters listed below are optional, and a reasonable default will
437             be used if left blank:
438              
439             =over 4
440              
441             =item DISPLAY_CLASS (default: Classic)
442              
443             the class used to display the login form. The alternative is C
444             which aims for XHTML compliance and leaving style to CSS. See
445             L for more details.
446              
447             =item TITLE (default: Sign In)
448              
449             the heading at the top of the login box
450              
451             =item USERNAME_LABEL (default: User Name)
452              
453             the label for the user name input
454              
455             =item PASSWORD_LABEL (default: Password)
456              
457             the label for the password input
458              
459             =item SUBMIT_LABEL (default: Sign In)
460              
461             the label for the submit button
462              
463             =item COMMENT (default: Please enter your username and password in the fields below.)
464              
465             a message provided on the first login attempt
466              
467             =item REMEMBERUSER_OPTION (default: 1)
468              
469             provide a checkbox to offer to remember the users name in a cookie so that
470             their user name will be pre-filled the next time they log in
471              
472             =item REMEMBERUSER_LABEL (default: Remember User Name)
473              
474             the label for the remember user name checkbox
475              
476             =item REMEMBERUSER_COOKIENAME (default: CAPAUTHTOKEN)
477              
478             the name of the cookie where the user name will be saved
479              
480             =item REGISTER_URL (default: )
481              
482             the URL for the register new account link
483              
484             =item REGISTER_LABEL (default: Register Now!)
485              
486             the label for the register new account link
487              
488             =item FORGOTPASSWORD_URL (default: )
489              
490             the URL for the forgot password link
491              
492             =item FORGOTPASSWORD_LABEL (default: Forgot Password?)
493              
494             the label for the forgot password link
495              
496             =item INVALIDPASSWORD_MESSAGE (default: Invalid username or password
(login attempt %d)
497              
498             a message given when a login failed
499              
500             =item INCLUDE_STYLESHEET (default: 1)
501              
502             use this to disable the built in style-sheet for the login box so you can provide your own custom styles
503              
504             =item FORM_SUBMIT_METHOD (default: post)
505              
506             use this to get the form to submit using 'get' instead of 'post'
507              
508             =item FOCUS_FORM_ONLOAD (default: 1)
509              
510             use this to automatically focus the login form when the page loads so a user can start typing right away.
511              
512             =item BASE_COLOUR (default: #445588)
513              
514             This is the base colour that will be used in the included login box. All other
515             colours are automatically calculated based on this colour (unless you hardcode
516             the colour values). In order to calculate other colours, you will need the
517             Color::Calc module. If you do not have the Color::Calc module, then you will
518             need to use fixed values for all of the colour options. All colour values
519             besides the BASE_COLOUR can be simple percentage values (including the % sign).
520             For example if you set the LIGHTER_COLOUR option to 80%, then the calculated
521             colour will be 80% lighter than the BASE_COLOUR.
522              
523             =item LIGHT_COLOUR (default: 50% or #a2aac4)
524              
525             A colour that is lighter than the base colour.
526              
527             =item LIGHTER_COLOUR (default: 75% or #d0d5e1)
528              
529             A colour that is another step lighter than the light colour.
530              
531             =item DARK_COLOUR (default: 30% or #303c5f)
532              
533             A colour that is darker than the base colour.
534              
535             =item DARKER_COLOUR (default: 60% or #1b2236)
536              
537             A colour that is another step darker than the dark colour.
538              
539             =item GREY_COLOUR (default: #565656)
540              
541             A grey colour that is calculated by desaturating the base colour.
542              
543             =back
544              
545             =back
546              
547             =cut
548              
549             sub config {
550 131     131 1 322 my $self = shift;
551 131 100       991 my $class = ref $self ? ref $self : $self;
552              
553 131 100 100     905 die "Calling config after the Authentication object has already been initialized"
554             if ref $self && defined $self->{initialized};
555 130         504 my $config = $self->_config;
556              
557 130 100       429 if (@_) {
558 129         198 my $props;
559 129 100       403 if ( ref( $_[0] ) eq 'HASH' ) {
560 46         65 my $rthash = %{ $_[0] };
  46         289  
561 46         271 $props = CGI::Application->_cap_hash( $_[0] );
562             } else {
563 83         6314 $props = CGI::Application->_cap_hash( {@_} );
564             }
565              
566             # Check for DRIVER
567 129 100       3882 if ( defined $props->{DRIVER} ) {
568 85 100 100     1044 croak "authen config error: parameter DRIVER is not a string or arrayref"
569             if ref $props->{DRIVER} && Scalar::Util::reftype( $props->{DRIVER} ) ne 'ARRAY';
570 84         400 $config->{DRIVER} = delete $props->{DRIVER};
571             # We will accept a string, or an arrayref of options, but what we really want
572             # is an array of arrayrefs of options, so that we can support multiple drivers
573             # each with their own custom options
574 31     31   27521 no warnings qw(uninitialized);
  31         67  
  31         4730  
575 84 100       485 $config->{DRIVER} = [ $config->{DRIVER} ] if Scalar::Util::reftype( $config->{DRIVER} ) ne 'ARRAY';
576 84 100       717 $config->{DRIVER} = [ $config->{DRIVER} ] if Scalar::Util::reftype( $config->{DRIVER}->[0] ) ne 'ARRAY';
577             }
578              
579             # Check for STORE
580 128 100       452 if ( defined $props->{STORE} ) {
581 87 100 100     1560 croak "authen config error: parameter STORE is not a string or arrayref"
582             if ref $props->{STORE} && Scalar::Util::reftype( $props->{STORE} ) ne 'ARRAY';
583 86         266 $config->{STORE} = delete $props->{STORE};
584             # We will accept a string, but what we really want is an arrayref of the store driver,
585             # and any custom options
586 31     31   162 no warnings qw(uninitialized);
  31         58  
  31         10945  
587 86 100       557 $config->{STORE} = [ $config->{STORE} ] if Scalar::Util::reftype( $config->{STORE} ) ne 'ARRAY';
588             }
589              
590             # Check for POST_LOGIN_RUNMODE
591 127 100       548 if ( defined $props->{POST_LOGIN_RUNMODE} ) {
592 6 100       39 croak "authen config error: parameter POST_LOGIN_RUNMODE is not a string"
593             if ref $props->{POST_LOGIN_RUNMODE};
594 5         20 $config->{POST_LOGIN_RUNMODE} = delete $props->{POST_LOGIN_RUNMODE};
595             }
596              
597             # Check for POST_LOGIN_URL
598 126 100       437 if ( defined $props->{POST_LOGIN_URL} ) {
599 10 100       93 carp "authen config warning: parameter POST_LOGIN_URL ignored since we already have POST_LOGIN_RUNMODE"
600             if $config->{POST_LOGIN_RUNMODE};
601 10 100       1986 croak "authen config error: parameter POST_LOGIN_URL is not a string"
602             if ref $props->{POST_LOGIN_URL};
603 9         32 $config->{POST_LOGIN_URL} = delete $props->{POST_LOGIN_URL};
604             }
605              
606             # Check for LOGIN_RUNMODE
607 125 100       443 if ( defined $props->{LOGIN_RUNMODE} ) {
608 4 100       27 croak "authen config error: parameter LOGIN_RUNMODE is not a string"
609             if ref $props->{LOGIN_RUNMODE};
610 3         10 $config->{LOGIN_RUNMODE} = delete $props->{LOGIN_RUNMODE};
611             }
612              
613             # Check for LOGIN_URL
614 124 100       777 if ( defined $props->{LOGIN_URL} ) {
615 7 100       45 carp "authen config warning: parameter LOGIN_URL ignored since we already have LOGIN_RUNMODE"
616             if $config->{LOGIN_RUNMODE};
617 7 100       771 croak "authen config error: parameter LOGIN_URL is not a string"
618             if ref $props->{LOGIN_URL};
619 6         19 $config->{LOGIN_URL} = delete $props->{LOGIN_URL};
620             }
621              
622             # Check for LOGOUT_RUNMODE
623 123 100       390 if ( defined $props->{LOGOUT_RUNMODE} ) {
624 4 100       28 croak "authen config error: parameter LOGOUT_RUNMODE is not a string"
625             if ref $props->{LOGOUT_RUNMODE};
626 3         15 $config->{LOGOUT_RUNMODE} = delete $props->{LOGOUT_RUNMODE};
627             }
628              
629             # Check for LOGOUT_URL
630 122 100       653 if ( defined $props->{LOGOUT_URL} ) {
631 7 100       38 carp "authen config warning: parameter LOGOUT_URL ignored since we already have LOGOUT_RUNMODE"
632             if $config->{LOGOUT_RUNMODE};
633 7 100       851 croak "authen config error: parameter LOGOUT_URL is not a string"
634             if ref $props->{LOGOUT_URL};
635 6         21 $config->{LOGOUT_URL} = delete $props->{LOGOUT_URL};
636             }
637              
638             # Check for CREDENTIALS
639 121 100       406 if ( defined $props->{CREDENTIALS} ) {
640 22 100 100     254 croak "authen config error: parameter CREDENTIALS is not a string or arrayref"
641             if ref $props->{CREDENTIALS} && Scalar::Util::reftype( $props->{CREDENTIALS} ) ne 'ARRAY';
642 21         81 $config->{CREDENTIALS} = delete $props->{CREDENTIALS};
643             # We will accept a string, but what we really want is an arrayref of the credentials
644 31     31   192 no warnings qw(uninitialized);
  31         65  
  31         158213  
645 21 100       124 $config->{CREDENTIALS} = [ $config->{CREDENTIALS} ] if Scalar::Util::reftype( $config->{CREDENTIALS} ) ne 'ARRAY';
646             }
647              
648             # Check for LOGIN_SESSION_TIMEOUT
649 120 100       400 if ( defined $props->{LOGIN_SESSION_TIMEOUT} ) {
650 20 100 100     138 croak "authen config error: parameter LOGIN_SESSION_TIMEOUT is not a string or a hashref"
651             if ref $props->{LOGIN_SESSION_TIMEOUT} && ref$props->{LOGIN_SESSION_TIMEOUT} ne 'HASH';
652 19         35 my $options = {};
653 19 100       73 if (! ref $props->{LOGIN_SESSION_TIMEOUT}) {
654 7         26 $options->{IDLE_FOR} = _time_to_seconds( $props->{LOGIN_SESSION_TIMEOUT} );
655 7 100       38 croak "authen config error: parameter LOGIN_SESSION_TIMEOUT is not a valid time string" unless defined $options->{IDLE_FOR};
656             } else {
657 12 100       62 if ($props->{LOGIN_SESSION_TIMEOUT}->{IDLE_FOR}) {
658 5         26 $options->{IDLE_FOR} = _time_to_seconds( delete $props->{LOGIN_SESSION_TIMEOUT}->{IDLE_FOR} );
659 5 100       31 croak "authen config error: IDLE_FOR option to LOGIN_SESSION_TIMEOUT is not a valid time string" unless defined $options->{IDLE_FOR};
660             }
661 11 100       38 if ($props->{LOGIN_SESSION_TIMEOUT}->{EVERY}) {
662 5         20 $options->{EVERY} = _time_to_seconds( delete $props->{LOGIN_SESSION_TIMEOUT}->{EVERY} );
663 5 100       30 croak "authen config error: EVERY option to LOGIN_SESSION_TIMEOUT is not a valid time string" unless defined $options->{EVERY};
664             }
665 10 100       35 if ($props->{LOGIN_SESSION_TIMEOUT}->{CUSTOM}) {
666 5         19 $options->{CUSTOM} = delete $props->{LOGIN_SESSION_TIMEOUT}->{CUSTOM};
667 5 100       34 croak "authen config error: CUSTOM option to LOGIN_SESSION_TIMEOUT must be a code reference" unless ref $options->{CUSTOM} eq 'CODE';
668             }
669 9 100       14 croak "authen config error: Invalid option(s) (" . join( ', ', keys %{$props->{LOGIN_SESSION_TIMEOUT}} ) . ") passed to LOGIN_SESSION_TIMEOUT" if %{$props->{LOGIN_SESSION_TIMEOUT}};
  1         19  
  9         39  
670             }
671              
672 14         47 $config->{LOGIN_SESSION_TIMEOUT} = $options;
673 14         43 delete $props->{LOGIN_SESSION_TIMEOUT};
674             }
675              
676             # Check for POST_LOGIN_CALLBACK
677 114 100       400 if ( defined $props->{POST_LOGIN_CALLBACK} ) {
678 33 100       166 croak "authen config error: parameter POST_LOGIN_CALLBACK is not a coderef"
679             unless( ref $props->{POST_LOGIN_CALLBACK} eq 'CODE' );
680 31         98 $config->{POST_LOGIN_CALLBACK} = delete $props->{POST_LOGIN_CALLBACK};
681             }
682              
683             # Check for RENDER_LOGIN
684 112 100       349 if ( defined $props->{RENDER_LOGIN} ) {
685 3 100       47 croak "authen config error: parameter RENDER_LOGIN is not a coderef"
686             unless( ref $props->{RENDER_LOGIN} eq 'CODE' );
687 1         4 $config->{RENDER_LOGIN} = delete $props->{RENDER_LOGIN};
688             }
689              
690             # Check for LOGIN_FORM
691 110 100       380 if ( defined $props->{LOGIN_FORM} ) {
692 26 100       141 croak "authen config error: parameter LOGIN_FORM is not a hashref"
693             unless( ref $props->{LOGIN_FORM} eq 'HASH' );
694 25         79 $config->{LOGIN_FORM} = delete $props->{LOGIN_FORM};
695             }
696              
697             # Check for DETAINT_URL_REGEXP
698 109 100       348 if ( defined $props->{DETAINT_URL_REGEXP} ) {
699 3         10 $config->{DETAINT_URL_REGEXP} = delete $props->{DETAINT_URL_REGEXP};
700             }
701             else {
702 106         296 $config->{DETAINT_URL_REGEXP} = '^([\w\_\%\?\&\;\-\/\@\.\+\$\=\#\:\!\*\"\'\(\)\,]+)$';
703             }
704              
705             # Check for DETAINT_USERNAME_REGEXP
706 109 100       292 if ( defined $props->{DETAINT_USERNAME_REGEXP} ) {
707 2         7 $config->{DETAINT_USERNAME_REGEXP} = delete $props->{DETAINT_USERNAME_REGEXP};
708             }
709             else {
710 107         270 $config->{DETAINT_USERNAME_REGEXP} = '^([\w\_]+)$';
711             }
712              
713             # If there are still entries left in $props then they are invalid
714 109 100       756 croak "Invalid option(s) (" . join( ', ', keys %$props ) . ") passed to config" if %$props;
715             }
716             }
717              
718             =head2 protected_runmodes
719              
720             This method takes a list of runmodes that are to be protected by authentication. If a user
721             tries to access one of these runmodes, then they will be redirected to a login page
722             unless they are properly logged in. The runmode names can be a list of simple strings, regular
723             expressions, or special directives that start with a colon. This method is cumulative, so
724             if it is called multiple times, the new values are added to existing entries. It returns
725             a list of all entries that have been saved so far. Calling this function,
726             will not itself generate cookies or session ids.
727              
728             =over 4
729              
730             =item :all - All runmodes in this module will require authentication
731              
732             =back
733              
734             # match all runmodes
735             __PACKAGE__->authen->protected_runmodes(':all');
736              
737             # only protect runmodes one two and three
738             __PACKAGE__->authen->protected_runmodes(qw(one two three));
739              
740             # protect only runmodes that start with auth_
741             __PACKAGE__->authen->protected_runmodes(qr/^auth_/);
742              
743             # protect all runmodes that *do not* start with public_
744             __PACKAGE__->authen->protected_runmodes(qr/^(?!public_)/);
745              
746             =cut
747              
748             sub protected_runmodes {
749 303     303 1 535 my $self = shift;
750 303         1069 my $config = $self->_config;
751              
752 303   100     1735 $config->{PROTECTED_RUNMODES} ||= [];
753 303 100       881 push @{$config->{PROTECTED_RUNMODES}}, @_ if @_;
  179         703  
754              
755 303         1009 return @{$config->{PROTECTED_RUNMODES}};
  303         8740  
756             }
757              
758             =head2 is_protected_runmode
759              
760             This method accepts the name of a runmode, and will tell you if that runmode is
761             a protected runmode (i.e. does a user need to be authenticated to access this runmode).
762             Calling this function, will not itself generate cookies or session ids.
763              
764             =cut
765              
766             sub is_protected_runmode {
767 119     119 1 263 my $self = shift;
768 119         223 my $runmode = shift;
769              
770 119         371 foreach my $runmode_test ($self->protected_runmodes) {
771 163 100 66     838 if (overload::StrVal($runmode_test) =~ /^Regexp=/) {
    100          
    100          
772             # We were passed a regular expression
773 9 100       125 return 1 if $runmode =~ $runmode_test;
774             } elsif (ref $runmode_test && ref $runmode_test eq 'CODE') {
775             # We were passed a code reference
776 8 100       95 return 1 if $runmode_test->($runmode);
777             } elsif ($runmode_test eq ':all') {
778             # all runmodes are protected
779 1         11 return 1;
780             } else {
781             # assume we were passed a string
782 145 100       2234 return 1 if $runmode eq $runmode_test;
783             }
784             }
785              
786             # See if the user is using attributes
787 19         105 my $sub = $self->_cgiapp->can($runmode);
788 19 100 100     520 return 1 if $sub && $RUNMODES{$sub};
789              
790 17         93 return;
791             }
792              
793             =head2 redirect_after_login
794              
795             This method is be called during the prerun stage to
796             redirect the user to the page that has been configured
797             as the destination after a successful login. The location is determined as follows:
798              
799             =over
800              
801             =item POST_LOGIN_RUNMODE
802              
803             If the POST_LOGIN_RUNMODE config parameter is set, that run mode will be the chosen location.
804              
805             =item POST_LOGIN_URL
806              
807             If the above fails and the POST_LOGIN_URL config parameter is set, then there will be a 302 redirection to that location.
808              
809             =item destination
810              
811             If the above fails and there is a destination query parameter, which must a taint check against the DETAINT_URL_REGEXP config parameter,
812             then there will be a 302 redirection to that location.
813              
814             =item original destination
815              
816             If all the above fail then there the originally requested page will be delivered.
817              
818             =back
819              
820             =cut
821              
822             sub redirect_after_login {
823 43     43 1 85 my $self = shift;
824 43         170 my $cgiapp = $self->_cgiapp;
825 43         121 my $config = $self->_config;
826              
827 43 100       286 if ($config->{POST_LOGIN_RUNMODE}) {
    100          
    100          
828 1         10 $cgiapp->prerun_mode($config->{POST_LOGIN_RUNMODE});
829             } elsif ($config->{POST_LOGIN_URL}) {
830 1         8 $cgiapp->header_add(-location => $config->{POST_LOGIN_URL});
831 1         61 $cgiapp->header_type('redirect');
832 1         22 $cgiapp->prerun_mode('authen_dummy_redirect');
833             } elsif (my $destination = $cgiapp->authen->_detaint_destination()) {
834 1         15 $cgiapp->header_add(-location => $destination);
835 1         68 $cgiapp->header_type('redirect');
836 1         25 $cgiapp->prerun_mode('authen_dummy_redirect');
837             }
838 43         295 return;
839             }
840              
841             =head2 redirect_to_login
842              
843             This method is be called during the prerun stage if
844             the current user is not logged in, and they are trying to
845             access a protected runmode. It will redirect to the page
846             that has been configured as the login page, based on the value
847             of LOGIN_RUNMODE or LOGIN_URL If nothing is configured
848             a simple login page will be automatically provided.
849              
850             =cut
851              
852             sub redirect_to_login {
853 94     94 1 194 my $self = shift;
854 94         276 my $cgiapp = $self->_cgiapp;
855 94         269 my $config = $self->_config;
856              
857 94 50       747 if ($config->{LOGIN_RUNMODE}) {
    50          
858 0         0 $cgiapp->prerun_mode($config->{LOGIN_RUNMODE});
859             } elsif ($config->{LOGIN_URL}) {
860 0         0 $cgiapp->header_add(-location => $config->{LOGIN_URL});
861 0         0 $cgiapp->header_type('redirect');
862 0         0 $cgiapp->prerun_mode('authen_dummy_redirect');
863             } else {
864 94         353 $cgiapp->prerun_mode('authen_login');
865             }
866             }
867              
868             =head2 redirect_to_logout
869              
870             This method is called during the prerun stage if the user
871             has requested to be logged out. It will redirect to the page
872             that has been configured as the logout page, based on the value
873             of LOGOUT_RUNMODE or LOGOUT_URL If nothing is
874             configured, the page will redirect to the website homepage.
875              
876             =cut
877              
878             sub redirect_to_logout {
879 1     1 1 2 my $self = shift;
880 1         4 my $cgiapp = $self->_cgiapp;
881 1         3 my $config = $self->_config;
882 1         6 $self->logout();
883              
884 1 50       7 if ($config->{LOGOUT_RUNMODE}) {
    50          
885 0         0 $cgiapp->prerun_mode($config->{LOGOUT_RUNMODE});
886             } elsif ($config->{LOGOUT_URL}) {
887 0         0 $cgiapp->header_add(-location => $config->{LOGOUT_URL});
888 0         0 $cgiapp->header_type('redirect');
889 0         0 $cgiapp->prerun_mode('authen_dummy_redirect');
890             } else {
891 1         6 $cgiapp->header_add(-location => '/');
892 1         55 $cgiapp->header_type('redirect');
893 1         20 $cgiapp->prerun_mode('authen_dummy_redirect');
894             }
895             }
896              
897             =head2 setup_runmodes
898              
899             This method is called during the prerun stage to register some custom
900             runmodes that the Authentication plugin requires in order to function.
901             Calling this function, will not itself generate cookies or session ids.
902              
903             =cut
904              
905             sub setup_runmodes {
906 151     151 1 331 my $self = shift;
907 151         416 my $config = $self->_config;
908              
909 151 50 33     1588 $self->_cgiapp->run_modes( authen_login => \&authen_login_runmode )
910             unless $config->{LOGIN_RUNMODE} || $config->{LOGIN_URL};
911 151 50 33     4666 $self->_cgiapp->run_modes( authen_logout => \&authen_logout_runmode )
912             unless $config->{LOGOUT_RUNMODE} || $config->{LOGOUT_URL};
913 151         2858 $self->_cgiapp->run_modes( authen_dummy_redirect => \&authen_dummy_redirect );
914 151         2806 return;
915             }
916              
917             =head2 last_login
918              
919             This will return return the time of the last login for this user
920              
921             my $last_login = $self->authen->last_login;
922              
923             This function will initiate a session or cookie if one has not been created already.
924              
925             =cut
926              
927             sub last_login {
928 5     5 1 11 my $self = shift;
929 5         10 my $new = shift;
930 5         18 $self->initialize;
931              
932 5 100       19 return unless $self->username;
933 3         12 my $old = $self->store->fetch('last_login');
934 3 50       24 $self->store->save('last_login' => $new) if $new;
935 3         29 return $old;
936             }
937              
938             =head2 last_access
939              
940             This will return return the time of the last access for this user
941              
942             my $last_access = $self->authen->last_access;
943              
944             This function will initiate a session or cookie if one has not been created already.
945              
946             =cut
947              
948             sub last_access {
949 9     9 1 19 my $self = shift;
950 9         22 my $new = shift;
951 9         28 $self->initialize;
952              
953 9 100       27 return unless $self->username;
954 4         13 my $old = $self->store->fetch('last_access');
955 4 100       37 $self->store->save('last_access' => $new) if $new;
956 4         42 return $old;
957             }
958              
959             =head2 is_login_timeout
960              
961             This will return true or false depending on whether the users login status just timed out
962              
963             $self->add_message('login session timed out') if $self->authen->is_login_timeout;
964              
965             This function will initiate a session or cookie if one has not been created already.
966              
967             =cut
968              
969             sub is_login_timeout {
970 5     5 1 11 my $self = shift;
971 5         18 $self->initialize;
972              
973 5 100       47 return $self->{is_login_timeout} ? 1 : 0;
974             }
975              
976             =head2 is_authenticated
977              
978             This will return true or false depending on the login status of this user
979              
980             assert($self->authen->is_authenticated); # The user should be logged in if we got here
981              
982             This function will initiate a session or cookie if one has not been created already.
983              
984             =cut
985              
986             sub is_authenticated {
987 232     232 1 516 my $self = shift;
988 232         656 $self->initialize;
989              
990 232 100       655 return $self->username ? 1 : 0;
991             }
992              
993             =head2 login_attempts
994              
995             This method will return the number of failed login attempts have been made by this
996             user since the last successful login. This is not a number that can be trusted,
997             as it is dependent on the underlying store to be able to return the correct value for
998             this user. For example, if the store uses a cookie based session, the user trying
999             to login could delete their cookies, and hence get a new session which will not have
1000             any login attempts listed. The number will be cleared upon a successful login.
1001             This function will initiate a session or cookie if one has not been created already.
1002              
1003             =cut
1004              
1005             sub login_attempts {
1006 120     120 1 250 my $self = shift;
1007 120         519 $self->initialize;
1008              
1009 120         586 my $la = $self->store->fetch('login_attempts');
1010 120         1218 return $la;
1011             }
1012              
1013             =head2 username
1014              
1015             This will return the username of the currently logged in user, or undef if
1016             no user is currently logged in.
1017              
1018             my $username = $self->authen->username;
1019              
1020             This function will initiate a session or cookie if one has not been created already.
1021              
1022             =cut
1023              
1024             sub username {
1025 508     508 1 1060 my $self = shift;
1026 508         1427 $self->initialize;
1027              
1028 508         1395 my $u = $self->store->fetch('username');
1029 506         5972 return $u;
1030             }
1031              
1032             =head2 is_new_login
1033              
1034             This will return true or false depending on if this is a fresh login
1035              
1036             $self->log->info("New Login") if $self->authen->is_new_login;
1037              
1038             This function will initiate a session or cookie if one has not been created already.
1039              
1040             =cut
1041              
1042             sub is_new_login {
1043 150     150 1 473 my $self = shift;
1044 150         388 $self->initialize;
1045              
1046 150         514 return $self->{is_new_login};
1047             }
1048              
1049             =head2 credentials
1050              
1051             This method will return the names of the form parameters that will be
1052             looked for during a login. By default they are authen_username and authen_password,
1053             but these values can be changed by supplying the CREDENTIALS parameters in the
1054             configuration. Calling this function, will not itself generate cookies or session ids.
1055              
1056             =cut
1057              
1058             sub credentials {
1059 203     203 1 2123 my $self = shift;
1060 203         857 my $config = $self->_config;
1061 203   100     1358 return $config->{CREDENTIALS} || [qw(authen_username authen_password)];
1062             }
1063              
1064             =head2 logout
1065              
1066             This will attempt to logout the user. If during a request the Authentication
1067             module sees a parameter called 'authen_logout', it will automatically call this method
1068             to log out the user.
1069              
1070             $self->authen->logout();
1071              
1072             This function will initiate a session or cookie if one has not been created already.
1073              
1074             =cut
1075              
1076             sub logout {
1077 5     5 1 14 my $self = shift;
1078 5         13 $self->initialize;
1079              
1080 5         12 $self->store->clear;
1081             }
1082              
1083             =head2 drivers
1084              
1085             This method will return a list of driver objects that are used for
1086             verifying the login credentials. Calling this function, will not itself generate cookies or session ids.
1087              
1088             =cut
1089              
1090             sub drivers {
1091 149     149 1 277 my $self = shift;
1092              
1093 149 100       708 if ( !$self->{drivers} ) {
1094 140         402 my $config = $self->_config;
1095              
1096             # Fetch the configuration parameters for the driver(s)
1097 140 100       561 my $driver_configs = defined $config->{DRIVER} ? $config->{DRIVER} : [['Dummy']];
1098              
1099 140         748 foreach my $driver_config (@$driver_configs) {
1100 196         827 my ($drivername, @params) = @$driver_config;
1101             # add support for Authen::Simple modules
1102 196 100       826 if (index($drivername, 'Authen::Simple') == 0) {
1103 2         7 unshift @params, $drivername;
1104 2         4 $drivername = 'Authen::Simple';
1105             }
1106             # Load the the class for this driver
1107 196   100     4308 my $driver_class = _find_deligate_class(
1108             'CGI::Application::Plugin::Authentication::Driver::' . $drivername,
1109             $drivername
1110             ) || die "Driver ".$drivername." can not be found";
1111              
1112             # Create the driver object
1113 195   100     24846 my $driver = $driver_class->new( $self, @params )
1114             || die "Could not create new $driver_class object";
1115 194         316 push @{$self->{drivers}}, $driver;
  194         1611  
1116             }
1117             }
1118              
1119 147         443 my $drivers = $self->{drivers};
1120 147         700 return @$drivers[0..$#$drivers];
1121             }
1122              
1123             =head2 store
1124              
1125             This method will return a store object that is used to store information
1126             about the status of the authentication across multiple requests.
1127             This function will initiate a session or cookie if one has not been created already.
1128              
1129             =cut
1130              
1131             sub store {
1132 811     811 1 1309 my $self = shift;
1133              
1134 811 100       11524 if ( !$self->{store} ) {
1135 164         399 my $config = $self->_config;
1136              
1137             # Fetch the configuration parameters for the store
1138 164         285 my ($store_module, @store_config);
1139 164 50 33     1624 ($store_module, @store_config) = @{ $config->{STORE} } if $config->{STORE} && ref $config->{STORE} eq 'ARRAY';
  164         2251  
1140 164 50       545 if (!$store_module) {
1141             # No STORE configuration was provided
1142 0 0 0     0 if ($self->_cgiapp->can('session') && UNIVERSAL::isa($self->_cgiapp->session, 'CGI::Session')) {
1143             # The user is already using the Session plugin
1144 0         0 ($store_module, @store_config) = ( 'Session' );
1145             } else {
1146             # Fall back to the Cookie Store
1147 0         0 ($store_module, @store_config) = ( 'Cookie' );
1148             }
1149             }
1150              
1151             # Load the the class for this store
1152 164   50     752 my $store_class = _find_deligate_class(
1153             'CGI::Application::Plugin::Authentication::Store::' . $store_module,
1154             $store_module
1155             ) || die "Store $store_module can not be found";
1156              
1157             # Create the store object
1158 164   50     8145 $self->{store} = $store_class->new( $self, @store_config )
1159             || die "Could not create new $store_class object";
1160             }
1161              
1162 810         3925 return $self->{store};
1163             }
1164              
1165             =head2 initialize
1166              
1167             This does most of the heavy lifting for the Authentication plugin. It will
1168             check to see if the user is currently attempting to login by looking for the
1169             credential form fields in the query object. It will load the required driver
1170             objects and authenticate the user. It is OK to call this method multiple times
1171             as it checks to see if it has already been executed and will just return
1172             without doing anything if called multiple times. This allows us to call
1173             initialize as late as possible in the request so that no unnecessary work is
1174             done.
1175              
1176             The user will be logged out by calling the C method if the login
1177             session has been idle for too long, if it has been too long since the last
1178             login, or if the login has timed out. If you need to know if a user was logged
1179             out because of a time out, you can call the C method.
1180              
1181             If all goes well, a true value will be returned, although it is usually not
1182             necessary to check.
1183              
1184             This function will initiate a session or cookie if one has not been created already.
1185              
1186             =cut
1187              
1188             sub initialize {
1189 1199     1199 1 2147 my $self = shift;
1190 1199 100       3724 return 1 if $self->{initialized};
1191              
1192             # It would seem to make more sense to do this at the /end/ of the routine
1193             # but that causes an infinite loop.
1194 165         485 $self->{initialized} = 1;
1195              
1196 165 50       573 if (UNIVERSAL::can($self->_cgiapp, 'devpopup')) {
1197 0         0 $self->_cgiapp->add_callback( 'devpopup_report', \&_devpopup_report );
1198             }
1199              
1200 165         491 my $config = $self->_config;
1201              
1202             # See if the user is trying to log in
1203             # We do this before checking to see if the user is already logged in, since
1204             # a logged in user may want to log in as a different user.
1205 165   100     978 my $field_names = $config->{CREDENTIALS} || [qw(authen_username authen_password)];
1206              
1207 165         527 my $query = $self->_cgiapp->query;
1208 165         6973 my @credentials = map { $query->param($_) } @$field_names;
  348         5058  
1209 165 100       3508 if ($credentials[0]) {
1210             # The user is trying to login
1211             # make sure if they are already logged in, that we log them out first
1212 130         444 my $store = $self->store;
1213 130 50       578 $store->clear if $store->fetch('username');
1214 130         2072 foreach my $driver ($self->drivers) {
1215 178 100       963 if (my $username = $driver->verify_credentials(@credentials)) {
1216             # This user provided the correct credentials
1217             # so save this new login in the store
1218 44         41219 my $now = time();
1219 44         433 $store->save( username => $username, login_attempts => 0, last_login => $now, last_access => $now );
1220 44         982 $self->{is_new_login} = 1;
1221             # See if we are remembering the username for this user
1222 44   100     300 my $login_config = $config->{LOGIN_FORM} || {};
1223 44 50 33     221 if ($login_config->{REMEMBERUSER_OPTION} && $query->param('authen_rememberuser')) {
1224 0   0     0 my $cookie = $query->cookie(
1225             -name => $login_config->{REMEMBERUSER_COOKIENAME} || 'CAPAUTHTOKEN',
1226             -value => $username,
1227             -expiry => '10y',
1228             );
1229 0         0 $self->_cgiapp->header_add(-cookie => [$cookie]);
1230             }
1231 44         136 last;
1232             }
1233             }
1234 117 100       721 unless ($self->username) {
1235             # password mismatch - increment failed login attempts
1236 73   100     247 my $attempts = $store->fetch('login_attempts') || 0;
1237 73         5501 $store->save( login_attempts => $attempts + 1 );
1238             }
1239              
1240 117 100       1402 $config->{POST_LOGIN_CALLBACK}->($self->_cgiapp)
1241             if($config->{POST_LOGIN_CALLBACK});
1242             }
1243              
1244             # Check the user name last of all because only this check might create a session behind the scenes.
1245             # In other words if a website works perfectly well without authentication,
1246             # then adding a protected run mode should not add session to the unprotected modes.
1247             # See 60_parsimony.t for the test.
1248 152 100 100     1665 if ($config->{LOGIN_SESSION_TIMEOUT} && !$self->{is_new_login} && $self->username) {
      100        
1249             # This is not a fresh login, and there are time out rules, so make sure the login is still valid
1250 4 100 100     50 if ($config->{LOGIN_SESSION_TIMEOUT}->{IDLE_FOR} && time() - $self->last_access >= $config->{LOGIN_SESSION_TIMEOUT}->{IDLE_FOR}) {
    100 100        
    100 66        
1251             # this login has been idle for too long
1252 1         3 $self->{is_login_timeout} = 1;
1253 1         5 $self->logout;
1254             } elsif ($config->{LOGIN_SESSION_TIMEOUT}->{EVERY} && time() - $self->last_login >= $config->{LOGIN_SESSION_TIMEOUT}->{EVERY}) {
1255             # it has been too long since the last login
1256 1         3 $self->{is_login_timeout} = 1;
1257 1         5 $self->logout;
1258             } elsif ($config->{LOGIN_SESSION_TIMEOUT}->{CUSTOM} && $config->{LOGIN_SESSION_TIMEOUT}->{CUSTOM}->($self)) {
1259             # this login has timed out
1260 1         9 $self->{is_login_timeout} = 1;
1261 1         6 $self->logout;
1262             }
1263             }
1264 152         502 return 1;
1265              
1266             }
1267              
1268             =head2 display
1269              
1270             This method will return the
1271             L object, creating
1272             and caching it if necessary.
1273              
1274             =cut
1275              
1276             sub display {
1277 204     204 1 118361 my $self = shift;
1278 204 100       1217 return $self->{display} if $self->{display};
1279 97   100     254 my $config = $self->_config->{LOGIN_FORM} || {};
1280 97   100     622 my $class = "CGI::Application::Plugin::Authentication::Display::".
1281             ($config->{DISPLAY_CLASS} || 'Classic');
1282 97         886 $class->require;
1283 97         4868 $self->{display} = $class->new($self->_cgiapp);
1284 97         715 return $self->{display};
1285             }
1286              
1287             =head2 login_box
1288              
1289             This method will return the HTML for a login box that can be
1290             embedded into another page. This is the same login box that is used
1291             in the default authen_login runmode that the plugin provides.
1292              
1293             This function will initiate a session or cookie if one has not been created already.
1294              
1295             =cut
1296              
1297             sub login_box {
1298 12     12 1 29 my $self = shift;
1299 12         51 return $self->display->login_box;
1300             }
1301              
1302             =head2 new
1303              
1304             This method creates a new CGI::Application::Plugin::Authentication object. It requires
1305             as it's only parameter a CGI::Application object. This method should never be called
1306             directly, since the 'authen' method that is imported into the CGI::Application module
1307             will take care of creating the CGI::Application::Plugin::Authentication object when it
1308             is required. Calling this function, will not itself generate cookies or session ids.
1309              
1310             =cut
1311              
1312             sub new {
1313 225     225 1 439 my $class = shift;
1314 225         421 my $cgiapp = shift;
1315 225         782 my $self = {};
1316              
1317 225         816 bless $self, $class;
1318 225         848 $self->{cgiapp} = $cgiapp;
1319 225         1327 Scalar::Util::weaken($self->{cgiapp}); # weaken circular reference
1320              
1321 225         736 return $self;
1322             }
1323              
1324             =head2 instance
1325              
1326             This method works the same way as 'new', except that it returns the same Authentication
1327             object for the duration of the request. This method should never be called
1328             directly, since the 'authen' method that is imported into the CGI::Application module
1329             will take care of creating the CGI::Application::Plugin::Authentication object when it
1330             is required. Calling this function, will not itself generate cookies or session ids.
1331              
1332             =cut
1333              
1334             sub instance {
1335 2092     2092 1 6593 my $class = shift;
1336 2092         3124 my $cgiapp = shift;
1337 2092 100 100     15101 die "CGI::Application::Plugin::Authentication->instance must be called with a CGI::Application object"
1338             unless defined $cgiapp && UNIVERSAL::isa( $cgiapp, 'CGI::Application' );
1339              
1340 2090 100       7941 $cgiapp->{__CAP_AUTHENTICATION_INSTANCE} = $class->new($cgiapp) unless defined $cgiapp->{__CAP_AUTHENTICATION_INSTANCE};
1341 2090         10251 return $cgiapp->{__CAP_AUTHENTICATION_INSTANCE};
1342             }
1343              
1344              
1345             =head1 CGI::Application CALLBACKS
1346              
1347             =head2 prerun_callback
1348              
1349             This method is a CGI::Application prerun callback that will be
1350             automatically registered for you if you are using CGI::Application
1351             4.0 or greater. If you are using an older version of CGI::Application
1352             you will have to create your own cgiapp_prerun method and make sure you
1353             call this method from there.
1354              
1355             sub cgiapp_prerun {
1356             my $self = shift;
1357              
1358             $self->CGI::Application::Plugin::Authentication::prerun_callback();
1359             }
1360              
1361             =cut
1362              
1363             sub prerun_callback {
1364 164     164 1 41033 my $self = shift;
1365 164         456 my $authen = $self->authen;
1366              
1367 164         622 $authen->initialize;
1368              
1369             # setup the default login and logout runmodes
1370 151         1038 $authen->setup_runmodes;
1371              
1372             # The user is asking to be logged out
1373 151 100       626 if ($self->query->param('authen_logout')) {
1374             # The user wants to logout
1375 1         32 return $self->authen->redirect_to_logout;
1376             }
1377              
1378             # If the user just logged in then we may want to redirect them
1379 150 100       4551 if ($authen->is_new_login) {
1380             # User just logged in, so where to we send them?
1381 43         244 return $self->authen->redirect_after_login;
1382             }
1383              
1384             # Update any time out info
1385 107         344 my $config = $authen->_config;
1386 107 100       374 if ( $config->{LOGIN_SESSION_TIMEOUT} ) {
1387             # update the last access time
1388 4         6 my $now = time;
1389 4         15 $authen->last_access($now);
1390             }
1391              
1392             # If a perun mode is set check against that.
1393             # This allows cooperation with plugins such as CAP::ActionDispatch
1394             # that also have preun hooks.
1395             # Note the comments in the CGI::Application docs on the ordering of
1396             # callback execution.
1397 107         652 my $run_mode = $self->prerun_mode;
1398 107   33     1803 $run_mode ||= $self->get_current_runmode;
1399            
1400 107 100       1084 if ( $authen->is_protected_runmode( $run_mode ) ) {
1401             # This runmode requires authentication
1402 96 100       363 unless ($authen->is_authenticated) {
1403             # This user is NOT logged in
1404 94         374 return $self->authen->redirect_to_login;
1405             }
1406             }
1407             }
1408              
1409             =head1 CGI::Application RUNMODES
1410              
1411             =head2 authen_login_runmode
1412              
1413             This runmode is provided if you do not want to create your
1414             own login runmode. It will display a simple login form for the user, which
1415             can be replaced by assigning RENDER_LOGIN a coderef that returns the HTML.
1416              
1417             =cut
1418              
1419             sub authen_login_runmode {
1420 94     94 1 12285 my $self = shift;
1421 94         272 my $authen = $self->authen;
1422              
1423 94         342 my $credentials = $authen->credentials;
1424 94         226 my $username = $credentials->[0];
1425 94         223 my $password = $credentials->[1];
1426              
1427 94         214 my $html;
1428 94 50       344 if ( my $sub = $authen->_config->{RENDER_LOGIN} ) {
1429 0         0 $html = $sub->($self);
1430             }
1431             else {
1432 94         516 $html = join( "\n",
1433             CGI::start_html( -title => $authen->display->login_title ),
1434             $authen->display->login_box,
1435             CGI::end_html(),
1436             );
1437             }
1438              
1439 94         5271 return $html;
1440             }
1441              
1442             =head2 authen_dummy_redirect
1443              
1444             This runmode is provided for convenience when an external redirect needs
1445             to be done. It just returns an empty string.
1446              
1447             =cut
1448              
1449             sub authen_dummy_redirect {
1450 3     3 1 281 return '';
1451             }
1452              
1453             ###
1454             ### Detainting helper methods
1455             ###
1456              
1457             sub _detaint_destination {
1458 161     161   306 my $self = shift;
1459 161         450 my $query = $self->_cgiapp->query;
1460 161         1797 my $destination = $query->param('destination');
1461 161         3290 my $regexp = $self->_config->{DETAINT_URL_REGEXP};
1462 161 100 100     4337 if ($destination && $destination =~ /$regexp/) {
1463 4         17 $destination = $1;
1464             }
1465             else {
1466 157         365 $destination = "";
1467             }
1468 161         1392 return $destination;
1469             }
1470              
1471             sub _detaint_selfurl {
1472 117     117   223 my $self = shift;
1473 117         311 my $query = $self->_cgiapp->query;
1474 117         1022 my $destination = "";
1475 117         302 my $regexp = $self->_config->{DETAINT_URL_REGEXP};
1476 117 100       3387 if ($query->self_url =~ /$regexp/) {
1477 115         348930 $destination = $1;
1478             }
1479 117         18670 return $destination;
1480             }
1481              
1482             sub _detaint_url {
1483 120     120   334 my $self = shift;
1484 120         380 my $query = $self->_cgiapp->query;
1485 120         1371 my $regexp = $self->_config->{DETAINT_URL_REGEXP};
1486 120         247 my $url = "";
1487 120 50       3956 if ($query->url( -absolute => 1, -path_info => 1 ) =~ /$regexp/) {
1488 0         0 $url = $1;
1489             }
1490 120         113509 return $url;
1491             }
1492              
1493             sub _detaint_username {
1494 105     105   223 my $self = shift;
1495 105         191 my $username = shift;
1496 105         186 my $cookiename = shift;
1497 105         281 my $query = $self->_cgiapp->query;
1498 105         1046 my $regexp = $self->_config->{DETAINT_USERNAME_REGEXP};
1499 105         219 my $username_value = "";
1500 105 100 100     386 if (($query->param($username) || $query->cookie($cookiename) || '') =~ /$regexp/) {
1501 60         3072 $username_value = $1;
1502             }
1503 105         50382 return $username_value;
1504             }
1505            
1506             ###
1507             ### Helper methods
1508             ###
1509              
1510             sub _cgiapp {
1511 1631     1631   9805 return $_[0]->{cgiapp};
1512             }
1513              
1514             sub _find_deligate_class {
1515 360     360   748 foreach my $class (@_) {
1516 468 100       5234 $class->require && return $class;
1517             }
1518 1         20 return;
1519             }
1520              
1521             sub _config {
1522 2501     2501   3639 my $self = shift;
1523 2501 100       6546 my $class = ref $self ? ref $self : $self;
1524 2501         3211 my $config;
1525 2501 100       4821 if ( ref $self ) {
1526 2472   100     9423 $config = $self->{__CAP_AUTHENTICATION_CONFIG} ||= $__CONFIG{$class} || {};
      66        
1527             } else {
1528 29   100     253 $__CONFIG{$class} ||= {};
1529 29         82 $config = $__CONFIG{$class};
1530             }
1531 2501         8480 return $config;
1532             }
1533              
1534             sub _devpopup_report {
1535 0     0   0 my $cgiapp = shift;
1536 0         0 my @list;
1537 0         0 my $self=$cgiapp->authen;
1538 0 0       0 if ($self->username) {
1539 0         0 push @list,['username',$self->username];
1540             }
1541 0         0 my $config = $self->_config;
1542 0   0     0 my $field_names = $config->{CREDENTIALS} || [qw(authen_username authen_password)];
1543 0         0 my $query = $cgiapp->query;
1544 0         0 foreach my $name (@$field_names) {
1545 0   0     0 push @list, [ $name, $query->param($name) || ''];
1546             }
1547 0         0 my $r=0;
1548 0         0 my $text = join $/, map {
1549 0         0 $r=1-$r;
1550 0 0       0 qq(
$_->[0]$_->[1]
  0         0  
1551             }
1552             @list;
1553 0         0 $cgiapp->devpopup->add_report(
1554             title => 'Authentication',
1555             summary => '',
1556             report => qq(
1557            
1560            
1561             $text
1562            
ParameterValue
1563            
1564            
1565            
1566             ),
1567             );
1568             }
1569              
1570             ###
1571             ### Helper functions
1572             ###
1573              
1574             sub _time_to_seconds {
1575 32     32   1269 my $time = shift;
1576 32 50       81 return unless defined $time;
1577              
1578             # Most of this function is borrowed from CGI::Util v1.4 by Lincoln Stein
1579 32         328 my (%mult) = (
1580             's' => 1,
1581             'm' => 60,
1582             'h' => 60 * 60,
1583             'd' => 60 * 60 * 24,
1584             'w' => 60 * 60 * 24 * 7,
1585             'M' => 60 * 60 * 24 * 30,
1586             'y' => 60 * 60 * 24 * 365
1587             );
1588             # format for time can be in any of the forms...
1589             # "180" -- in 180 seconds
1590             # "180s" -- in 180 seconds
1591             # "2m" -- in 2 minutes
1592             # "12h" -- in 12 hours
1593             # "1d" -- in 1 day
1594             # "4w" -- in 4 weeks
1595             # "3M" -- in 3 months
1596             # "2y" -- in 2 years
1597 32         43 my $offset;
1598 32 100       181 if ( $time =~ /^([+-]?(?:\d+|\d*\.\d*))([smhdwMy]?)$/ ) {
1599 29 100 100     224 return if (!$2 || $2 eq 's') && $1 != int $1; #
      100        
1600 28   100     204 $offset = int ( ( $mult{$2} || 1 ) * $1 );
1601             }
1602 31         161 return $offset;
1603             }
1604              
1605              
1606             =head1 EXAMPLE
1607              
1608             In a CGI::Application module:
1609              
1610             use base qw(CGI::Application);
1611             use CGI::Application::Plugin::AutoRunmode;
1612             use CGI::Application::Plugin::Session;
1613             use CGI::Application::Plugin::Authentication;
1614              
1615             __PACKAGE__->authen->config(
1616             DRIVER => [ 'Generic', { user1 => '123' } ],
1617             STORE => 'Session',
1618             LOGOUT_RUNMODE => 'start',
1619             );
1620             __PACKAGE__->authen->protected_runmodes(qr/^auth_/, 'one');
1621              
1622             sub start : RunMode {
1623             my $self = shift;
1624              
1625             }
1626              
1627             sub one : RunMode {
1628             my $self = shift;
1629              
1630             # The user will only get here if they are logged in
1631             }
1632              
1633             sub auth_two : RunMode {
1634             my $self = shift;
1635              
1636             # This is also protected because of the
1637             # regexp call to protected_runmodes above
1638             }
1639              
1640             =head1 COMPATIBILITY WITH L
1641              
1642             The prerun callback has been modified so that it will check for the presence of a prerun mode.
1643             This is for compatibility with L. This
1644             change should be considered experimental. It is necessary to load the ActionDispatch
1645             module so that the two prerun callbacks will be called in the correct order.
1646              
1647             =head1 RECOMMENDED USAGE
1648              
1649             =over
1650              
1651             =item CSS
1652              
1653             The best practice nowadays is generally considered to be to not have CSS
1654             embedded in HTML. Thus it should be best to set LOGIN_FORM -> DISPLAY_CLASS to
1655             'Basic'.
1656              
1657             =item Post login destination
1658              
1659             Of the various means of selecting a post login destination the most secure would
1660             seem to be POST_LOGIN_URL. The C parameter could potentially be hijacked by hackers.
1661             The POST_LOGIN_RUNMODE parameter requires a hidden parameter that could potentially
1662             be hijacked.
1663              
1664             =item Taint mode
1665              
1666             Do run your code under taint mode. It should help protect your application
1667             against a number of attacks.
1668              
1669             =item URL and username checking
1670              
1671             Please set the C and C parameters
1672             as tightly as possible. In particular you should prevent the destination parameter
1673             being used to redirect authenticated users to external sites; unless of course that
1674             is what you want in which case that site should be the only possible external site.
1675              
1676             =item The login form
1677              
1678             The HTML currently generated does not seem to be standards compliant as per
1679             RT bug 58023. Also the default login form includes hidden forms which could
1680             conceivably be hijacked.
1681             Set LOGIN_FORM -> DISPLAY_CLASS to 'Basic' to fix this.
1682              
1683             =back
1684              
1685             =head1 TODO
1686              
1687             There are lots of things that can still be done to improve this plugin. If anyone else is interested
1688             in helping out feel free to dig right in. Many of these things don't need my input, but if you want
1689             to avoid duplicated efforts, send me a note, and I'll let you know of anyone else is working in the same area.
1690              
1691             =over 4
1692              
1693             =item review the code for security bugs and report
1694              
1695             =item complete the separation of presentation and logic
1696              
1697             =item write a tutorial
1698              
1699             =item build more Drivers (Class::DBI, LDAP, Radius, etc...)
1700              
1701             =item Add support for method attributes to identify runmodes that require authentication
1702              
1703             =item finish the test suite
1704              
1705             =item provide more example code
1706              
1707             =item clean up the documentation
1708              
1709             =item build a DB driver that builds it's own table structure. This can be used by people that don't have their own user database to work with, and could include a simple user management application.
1710              
1711              
1712             =back
1713              
1714             =head1 BUGS
1715              
1716             This is alpha software and as such, the features and interface
1717             are subject to change. So please check the Changes file when upgrading.
1718              
1719             Some of the test scripts appear to be incompatible with versions of
1720             L later than 0.65.
1721              
1722             =head1 SEE ALSO
1723              
1724             L, perl(1)
1725              
1726              
1727             =head1 AUTHOR
1728              
1729             Author: Cees Hek ; Co-maintainer: Nicholas Bamber .
1730              
1731             =head1 CREDITS
1732              
1733             Thanks to L for funding the
1734             development of this plugin and for releasing it to the world.
1735              
1736             Thanks to Christian Walde for suggesting changes to fix the incompatibility with
1737             L and for help with github.
1738              
1739             Thanks to Alexandr Ciornii for pointing out some typos.
1740              
1741             =head1 LICENCE AND COPYRIGHT
1742              
1743             Copyright (c) 2005, SiteSuite. All rights reserved.
1744             Copyright (c) 2010, Nicholas Bamber. (Portions of the code).
1745              
1746             This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
1747              
1748             The background images in the default login forms are used courtesy of
1749             L. Those icons
1750             are issued under the
1751             L.
1752             Those icons are copyrighted 2006 by Mark James
1753              
1754             =head1 DISCLAIMER OF WARRANTY
1755              
1756             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
1757              
1758             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
1759              
1760             =cut
1761              
1762             1;