1package CGI::Application::Plugin::Authentication::Display::Classic;
2$CGI::Application::Plugin::Authentication::Display::Classic::VERSION = '0.23';
3use base qw(CGI::Application::Plugin::Authentication::Display);
4
5use 5.006;
6use strict;
7use warnings;
8use Carp;
9
10sub new {
11    my $class = shift;
12    my $cgiapp = shift;
13    my $self = CGI::Application::Plugin::Authentication::Display->new($cgiapp);
14    bless $self, $class;
15    return $self;
16}
17
18sub login_box {
19    my $self        = shift;
20    my $credentials = $self->_cgiapp->authen->credentials;
21    my $runmode     = $self->_cgiapp->get_current_runmode;
22    my $destination = $self->_cgiapp->authen->_detaint_destination || $self->_cgiapp->authen->_detaint_selfurl;
23    my $action      = $self->_cgiapp->authen->_detaint_url;
24    my $username    = $credentials->[0];
25    my $password    = $credentials->[1];
26    my $login_form  = $self->_cgiapp->authen->_config->{LOGIN_FORM} || {};
27    my %options = (
28        TITLE                   => 'Sign In',
29        USERNAME_LABEL          => 'User Name',
30        PASSWORD_LABEL          => 'Password',
31        SUBMIT_LABEL            => 'Sign In',
32        COMMENT                 => 'Please enter your username and password in the fields below.',
33        REMEMBERUSER_OPTION     => 1,
34        REMEMBERUSER_LABEL      => 'Remember User Name',
35        REMEMBERUSER_COOKIENAME => 'CAPAUTHTOKEN',
36        REGISTER_URL            => '',
37        REGISTER_LABEL          => 'Register Now!',
38        FORGOTPASSWORD_URL      => '',
39        FORGOTPASSWORD_LABEL    => 'Forgot Password?',
40        INVALIDPASSWORD_MESSAGE => 'Invalid username or password<br />(login attempt %d)',
41        INCLUDE_STYLESHEET      => 1,
42        FORM_SUBMIT_METHOD      => 'post',
43        %$login_form,
44    );
45
46    my $messages = '';
47    if ( my $attempts = $self->_cgiapp->authen->login_attempts ) {
48        $messages .= '<li class="warning">' . sprintf($options{INVALIDPASSWORD_MESSAGE}, $attempts) . '</li>';
49    } elsif ($options{COMMENT}) {
50        $messages .= "<li>$options{COMMENT}</li>";
51    }
52
53    my $tabindex = 3;
54    my ($rememberuser, $username_value, $register, $forgotpassword, $javascript, $style) = ('','','','','','');
55    if ($options{FOCUS_FORM_ONLOAD}) {
56        $javascript .= "document.loginform.${username}.focus();\n";
57    }
58    if ($options{REMEMBERUSER_OPTION}) {
59        $rememberuser = qq[<input id="authen_rememberuserfield" tabindex="$tabindex" type="checkbox" name="authen_rememberuser" value="1" />$options{REMEMBERUSER_LABEL}<br />];
60        $tabindex++;
61        $username_value = $self->_cgiapp->authen->_detaint_username($username, $options{REMEMBERUSER_COOKIENAME});
62        $javascript .= "document.loginform.${username}.select();\n" if $username_value;
63    }
64    my $submit_tabindex = $tabindex++;
65    if ($options{REGISTER_URL}) {
66        $register = qq[<a href="$options{REGISTER_URL}" id="authen_registerlink" tabindex="$tabindex">$options{REGISTER_LABEL}</a>];
67        $tabindex++;
68    }
69    if ($options{FORGOTPASSWORD_URL}) {
70        $forgotpassword = qq[<a href="$options{FORGOTPASSWORD_URL}" id="authen_forgotpasswordlink" tabindex="$tabindex">$options{FORGOTPASSWORD_LABEL}</a>];
71        $tabindex++;
72    }
73    if ($options{INCLUDE_STYLESHEET}) {
74        my $login_styles = $self->_login_styles;
75        $style = <<EOS;
76<style type="text/css">
77<!--/* <![CDATA[ */
78$login_styles
79/* ]]> */-->
80</style>
81EOS
82    }
83    if ($javascript) {
84        $javascript = qq[<script type="text/javascript" language="JavaScript">$javascript</script>];
85    }
86
87    my $html .= <<END;
88$style
89<form name="loginform" method="$options{FORM_SUBMIT_METHOD}" action="${action}">
90  <div class="login">
91    <div class="login_header">
92      $options{TITLE}
93    </div>
94    <div class="login_content">
95      <ul class="message">
96${messages}
97      </ul>
98      <fieldset>
99        <label for="${username}">$options{USERNAME_LABEL}</label>
100        <input id="authen_loginfield" tabindex="1" type="text" name="${username}" size="20" value="$username_value" /><br />
101        <label for="${password}">$options{PASSWORD_LABEL}</label>
102        <input id="authen_passwordfield" tabindex="2" type="password" name="${password}" size="20" /><br />
103        ${rememberuser}
104      </fieldset>
105    </div>
106    <div class="login_footer">
107      <div class="buttons">
108        <input id="authen_loginbutton" tabindex="${submit_tabindex}" type="submit" name="authen_loginbutton" value="$options{SUBMIT_LABEL}" class="button" />
109        ${register}
110        ${forgotpassword}
111      </div>
112    </div>
113  </div>
114  <input type="hidden" name="destination" value="${destination}" />
115  <input type="hidden" name="rm" value="${runmode}" />
116</form>
117$javascript
118END
119
120    return $html;
121}
122
123sub _login_styles {
124    my $self = shift;
125    my $login_form  = $self->_cgiapp->authen->_config->{LOGIN_FORM} || {};
126    my %colour = ();
127
128    $colour{base}    = $login_form->{BASE_COLOUR} || '#445588';
129    $colour{lighter} = $login_form->{LIGHTER_COLOUR} if $login_form->{LIGHTER_COLOUR};
130    $colour{light}   = $login_form->{LIGHT_COLOUR} if $login_form->{LIGHT_COLOUR};
131    $colour{dark}    = $login_form->{DARK_COLOUR} if $login_form->{DARK_COLOUR};
132    $colour{darker}  = $login_form->{DARKER_COLOUR} if $login_form->{DARKER_COLOUR};
133    $colour{grey}    = $login_form->{GREY_COLOUR} if $login_form->{GREY_COLOUR};
134
135    my @undefined_colours =  grep { ! defined $colour{$_} || index($colour{$_}, '%') >= 0 } qw(lighter light dark darker);
136    if (@undefined_colours) {
137        eval { require Color::Calc };
138        if ($@ && $login_form->{BASE_COLOUR}) {
139            warn "Color::Calc is required when specifying a custom BASE_COLOUR, and leaving LIGHTER_COLOUR, LIGHT_COLOUR, DARK_COLOUR or DARKER_COLOUR blank or when providing percentage based colour";
140        }
141        if ($@) {
142            $colour{base}    = '#445588';
143            $colour{lighter} = '#d0d5e1';
144            $colour{light}   = '#a2aac4';
145            $colour{dark}    = '#303c5f';
146            $colour{darker}  = '#1b2236';
147            $colour{grey}    = '#565656';
148        } else {
149            $colour{lighter} = !$colour{lighter}
150                                    ? Color::Calc::light_html($colour{base}, 0.75)
151                             : $colour{lighter} =~ m#(\d{2})%#
152                                    ? Color::Calc::light_html($colour{base}, $1 / 100)
153                             : $colour{lighter};
154            $colour{light}   = !$colour{light}
155                                    ? Color::Calc::light_html($colour{base}, 0.5)
156                             : $colour{light} =~ m#(\d{2})%#
157                                    ? Color::Calc::light_html($colour{base}, $1 / 100)
158                             : $colour{light};
159            $colour{dark}    = !$colour{dark}
160                                    ? Color::Calc::dark_html($colour{base}, 0.3)
161                             : $colour{dark} =~ m#(\d{2})%#
162                                    ? Color::Calc::dark_html($colour{base}, $1 / 100)
163                             : $colour{dark};
164            $colour{darker}  = !$colour{darker}
165                                    ? Color::Calc::dark_html($colour{base}, 0.6)
166                             : $colour{darker} =~ m#(\d{2})%#
167                                    ? Color::Calc::dark_html($colour{base}, $1 / 100)
168                             : $colour{darker};
169            #$colour{grey}    ||= Color::Calc::bw_html($colour{base});
170            if (!$colour{grey}) {
171                $colour{grey} = Color::Calc::bw_html($colour{base});
172            }
173        }
174    }
175    $colour{grey} ||= '#565656';
176    return <<END;
177div.login {
178  width: 25em;
179  margin: auto;
180  padding: 3px;
181  font-weight: bold;
182  border: 2px solid $colour{base};
183  color: $colour{dark};
184  font-family: sans-serif;
185}
186div.login div {
187  margin: 0;
188  padding: 0;
189  border: none;
190}
191div.login .login_header {
192  background: $colour{base};
193  border-bottom: 1px solid $colour{darker};
194  height: 1.5em;
195  padding: 0.45em;
196  text-align: left;
197  color: #fff;
198  font-size: 100%;
199  font-weight: bold;
200}
201div.login .login_content {
202  background: $colour{lighter};
203  padding: 0.8em;
204  border-top: 1px solid white;
205  border-bottom: 1px solid $colour{grey};
206  font-size: 80%;
207}
208div.login .login_footer {
209  background: $colour{light};
210  border-top: 1px solid white;
211  border-bottom: 1px solid white;
212  text-align: left;
213  padding: 0;
214  margin: 0;
215  min-height: 2.8em;
216}
217div.login fieldset {
218  margin: 0;
219  padding: 0;
220  border: none;
221  width: 100%;
222}
223div.login label {
224  clear: left;
225  float: left;
226  padding: 0.6em 1em 0.6em 0;
227  width: 8em;
228  text-align: right;
229}
230/* image courtesy of http://www.famfamfam.com/lab/icons/silk/  */
231#authen_loginfield {
232  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG5SURBVHjaYvz//z8DJQAggFiIVfh0twHn9w8KD9+/ZBT+9/cfExfvwwc87GxWAAFEtAFf3yl++/9XikHXL56BkYmJ4dKmcoUPT99PBQggRmK8ALT9v4BUBQMLrxxQMztY7N+PjwyXtk76BxBATMRoFjGewsDCx8jw9Oxyht9vboIxCDAxs/wCCCC8LoBrZv/A8PPpVoZ/39gZ7p57xcDLJ8Xw5tkdBrO8DYwAAcRElOYXaxn+/73DwC4vzyAmzsLw58kJsGaQOoAAYiJK868nDGwSXgxvjp1n+Hz7HoNawRFGmFqAAMIw4MBEDaI1gwBAAKEYsKtL/b9x2HSiNYMAQACBA3FmiqKCohrbfQ2nLobn97Yz6Br/JEozCAAEEDgh/eb6d98yYhEDBxsnw5VNZxnOffjLIKltw/D52B6GH89fMVjUnGbEFdgAAQRPiexMzAyfDk9gMJbmYbh17irDueMrGbjExBi8Oy8z4ksnAAEENuDY1S8MjjsnMSgaezJ8Z2Bm+P95PgPX6ycENYMAQACBwyDSUeQ/GzB926kLMEjwsjOwifKvcy05EkxMHgEIIEZKszNAgAEA+j3MEVmacXUAAAAASUVORK5CYII=') no-repeat 0 1px;
233  background-color: #fff;
234  border-top: solid 1px $colour{grey};
235  border-left: solid 1px $colour{grey};
236  border-bottom: solid 1px $colour{light};
237  border-right: solid 1px $colour{light};
238  padding: 2px 0 2px 18px;
239  margin: 0.3em 0;
240  width: 12em;
241}
242/* image courtesy of http://www.famfamfam.com/lab/icons/silk/  */
243#authen_passwordfield {
244  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKbSURBVHjaYvz//z8DPvBko+s0IJUJ5U6X8d+dhSwPEEAMIANw4ccbXKYB8f8/P+6BMYgNEkNWAxBAhDV/Pff/5+t5/39/2gcU/gc25P5qpzkwdQABxIjNCzBnS7p2Mfz5tJ+BkVWE4dWRxWA5oBcYHiyyYnj5heGAedYxR4AAwmXAf0mPWQx/3q9n+P/3I9AAMaCoBsPr4x0MDH/+MUgHrGG4P8eF4fVf9gMAAcSEK/D+/3oA1gxm/3kLJG8wSDhWMAjoeTJ8fxjNoJDQzyD0+7sDQACx4DKAkVWcgZGZG2jIV6AJfxn+/37F8OfPO6BhRxl+f/nIwC7xluHPm58MAAHEhMX5ILHp787OYvj/7zvDr7f7Gf59vw804DUwPM4x/P3+loFb0ZfhVlc1wxMu7psAAcSCEd9MjAzswoYMAppmDD9e9DKwcIkwMHFyMPx+dZnh7+9vDDxqwQx3Ji1jeMrJc9W1/JQOQAAheyFT2mctw9+vpxh+fz7A8O1JDQMrEz/QK2YMb47uZpD0SmEAmsRwu7eJ4QUX1wWXklOGIE0AAcQIim9YShOzSmf49W4xw5+PdxlYeIUYWLh9GS6vXPH+3U/Gd3K/vikzcTAzvOTkOmNXeNIUZitAALFAbF4D9N8Bhl+vJjP8/vCUgY1fkoGZ24PhysoV7178Y9vmW3M8FqZBHS3MAAIIZMDnP59P835/3Mnw98t7Bg5xNQZGNnOgzSvfv2ZgX+dbfiwVX14BCCCQAbyMrNwMDKxcDOxi/Az/WU0YLi1b8/E9K8cqr6JjGQwEAEAAMf378+/cn+//GFi5bRiYuMOBzt7w4RMH50IPIjSDAEAAsbz8+Gfdh9VFEr9//WX7//s/009uzlmuWUcqGYgEAAEGAIZWUhP4bjW1AAAAAElFTkSuQmCC') no-repeat 0 1px;
245  background-color: #fff;
246  border-top: solid 1px $colour{grey};
247  border-left: solid 1px $colour{grey};
248  border-bottom: solid 1px $colour{light};
249  border-right: solid 1px $colour{light};
250  padding: 2px 0 2px 18px;
251  margin: 0.3em 0;
252  width: 12em;
253}
254#authen_rememberuserfield {
255  clear: left;
256  margin-left: 8em;
257}
258#authen_loginfield:focus {
259  background-color: #ffc;
260  color: #000;
261}
262#authen_passwordfield:focus {
263  background-color: #ffc;
264  color: #000;
265}
266div.login a {
267  font-size: 80%;
268  color: $colour{dark};
269}
270div.login div.buttons input {
271  border-top: solid 2px $colour{light};
272  border-left: solid 2px $colour{light};
273  border-bottom: solid 2px $colour{grey};
274  border-right: solid 2px $colour{grey};
275  background-color: $colour{lighter};
276  padding: .2em 1em ;
277  font-size: 80%;
278  font-weight: bold;
279  color: $colour{dark};
280}
281div.login div.buttons {
282  display: block;
283  margin: 8px 4px;
284  width: 100%;
285}
286#authen_loginbutton {
287  float: right;
288  margin-right: 1em;
289}
290#authen_registerlink {
291  display: block;
292}
293#authen_forgotpasswordlink {
294  display: block;
295}
296ul.message {
297  margin-top: 0;
298  margin-bottom: 0;
299  list-style: none;
300}
301ul.message li {
302  text-indent: -2em;
303  padding: 0px;
304  margin: 0px;
305  font-style: italic;
306}
307ul.message li.warning {
308  color: red;
309}
310END
311}
312
313=head1 NAME
314
315CGI::Application::Plugin::Authentication::Display::Classic - login box that works out of the box
316
317=head1 DESCRIPTION
318
319This module provides a login box that works out of the box but which can be
320configured to modify the styling.
321
322=head1 METHODS
323
324=head2 new
325
326The constructor must be passed the L<CGI::Application> object as the first
327non-object argument.
328
329=head2 login_box
330
331This method will return the HTML for a login box that can be
332embedded into another page.  This is the same login box that is used
333in the default authen_login runmode that the plugin provides.
334
335You can set this option to customize the login form that is created when a user
336needs to be authenticated.  If you wish to replace the entire login form with a
337completely custom version, then just set LOGIN_RUNMODE to point to your custom
338runmode.
339
340All of the parameters listed below are optional, and a reasonable default will
341be used if left blank:
342
343=over 4
344
345=item TITLE (default: Sign In)
346
347the heading at the top of the login box
348
349=item USERNAME_LABEL (default: User Name)
350
351the label for the user name input
352
353=item PASSWORD_LABEL (default: Password)
354
355the label for the password input
356
357=item SUBMIT_LABEL (default: Sign In)
358
359the label for the submit button
360
361=item COMMENT (default: Please enter your username and password in the fields below.)
362
363a message provided on the first login attempt
364
365=item REMEMBERUSER_OPTION (default: 1)
366
367provide a checkbox to offer to remember the users name in a cookie so that
368their user name will be pre-filled the next time they log in
369
370=item REMEMBERUSER_LABEL (default: Remember User Name)
371
372the label for the remember user name checkbox
373
374=item REMEMBERUSER_COOKIENAME (default: CAPAUTHTOKEN)
375
376the name of the cookie where the user name will be saved
377
378=item REGISTER_URL (default: <none>)
379
380the URL for the register new account link
381
382=item REGISTER_LABEL (default: Register Now!)
383
384the label for the register new account link
385
386=item FORGOTPASSWORD_URL (default: <none>)
387
388the URL for the forgot password link
389
390=item FORGOTPASSWORD_LABEL (default: Forgot Password?)
391
392the label for the forgot password link
393
394=item INVALIDPASSWORD_MESSAGE (default: Invalid username or password<br />(login attempt %d)
395
396a message given when a login failed
397
398=item INCLUDE_STYLESHEET (default: 1)
399
400use this to disable the built in style-sheet for the login box so you can provide your own custom styles
401
402=item FORM_SUBMIT_METHOD (default: post)
403
404use this to get the form to submit using 'get' instead of 'post'
405
406=item FOCUS_FORM_ONLOAD (default: 1)
407
408use this to automatically focus the login form when the page loads so a user can start typing right away.
409
410=item BASE_COLOUR (default: #445588)
411
412This is the base colour that will be used in the included login box.  All other
413colours are automatically calculated based on this colour (unless you hardcode
414the colour values).  In order to calculate other colours, you will need the
415Color::Calc module.  If you do not have the Color::Calc module, then you will
416need to use fixed values for all of the colour options.  All colour values
417besides the BASE_COLOUR can be simple percentage values (including the % sign).
418For example if you set the LIGHTER_COLOUR option to 80%, then the calculated
419colour will be 80% lighter than the BASE_COLOUR.
420
421=item LIGHT_COLOUR (default: 50% or #a2aac4)
422
423A colour that is lighter than the base colour.
424
425=item LIGHTER_COLOUR (default: 75% or #d0d5e1)
426
427A colour that is another step lighter than the light colour.
428
429=item DARK_COLOUR (default: 30% or #303c5f)
430
431A colour that is darker than the base colour.
432
433=item DARKER_COLOUR (default: 60% or #1b2236)
434
435A colour that is another step darker than the dark colour.
436
437=item GREY_COLOUR (default: #565656)
438
439A grey colour that is calculated by desaturating the base colour.
440
441
442=back
443
444  LOGIN_FORM => {
445    TITLE              => 'Login',
446    SUBMIT_LABEL       => 'Login',
447    REMEMBERUSER_LABEL => 1,
448    BASE_COLOUR        => '#0099FF',
449    LIGHTER_COLOUR     => '#AAFFFF',
450    DARK_COLOUR        => '50%',
451  }
452
453=head1 BUGS
454
455This is alpha software and as such, the features and interface
456are subject to change.  So please check the Changes file when upgrading.
457
458=head1 SEE ALSO
459
460L<CGI::Application>, perl(1)
461
462=head1 AUTHOR
463
464Author: Cees Hek <ceeshek@gmail.com>; Co-maintainer: Nicholas Bamber <nicholas@periapt.co.uk>.
465
466=head1 CREDITS
467
468Thanks to SiteSuite (http://www.sitesuite.com.au) for funding the
469development of this plugin and for releasing it to the world.
470
471Thanks to Christian Walde for suggesting changes to fix the incompatibility with
472L<CGI::Application::Plugin::ActionDispatch> and for help with github.
473
474=head1 LICENCE AND COPYRIGHT
475
476Copyright (c) 2005, SiteSuite. All rights reserved.
477Copyright (c) 2010, Nicholas Bamber. All rights reserved.
478
479This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
480
481=head1 DISCLAIMER OF WARRANTY
482
483BECAUSE 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.
484
485IN 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.
486
487=cut
488
4891;
490