File Coverage

blib/lib/Plack/Middleware/Auth/Form.pm
Criterion Covered Total %
statement 85 88 96.5
branch 35 42 83.3
condition 13 25 52.0
subroutine 14 15 93.3
pod 2 2 100.0
total 149 172 86.6


line stmt bran cond sub pod time code
1 3     3   40295 use strict;
  3         7  
  3         100  
2 3     3   17 use warnings;
  3         4  
  3         181  
3             package Plack::Middleware::Auth::Form;
4             {
5             $Plack::Middleware::Auth::Form::VERSION = '0.012';
6             }
7              
8 3     3   900 use parent qw/Plack::Middleware/;
  3         286  
  3         23  
9 3     3   36024 use Plack::Util::Accessor qw( secure authenticator no_login_page after_logout ssl_port );
  3         6  
  3         25  
10 3     3   2761 use Plack::Request;
  3         181200  
  3         105  
11 3     3   28 use Scalar::Util;
  3         5  
  3         134  
12 3     3   14 use Carp ();
  3         6  
  3         3148  
13              
14              
15             sub prepare_app {
16 12     12 1 30977 my $self = shift;
17              
18 12 50       38 my $auth = $self->authenticator or Carp::croak 'authenticator is not set';
19 12 50 33     242 if (Scalar::Util::blessed($auth) && $auth->can('authenticate')) {
    50          
20 0     0   0 $self->authenticator(sub { $auth->authenticate(@_[0,1]) }); # because Authen::Simple barfs on 3 params
  0         0  
21             } elsif (ref $auth ne 'CODE') {
22 0         0 die 'authenticator should be a code reference or an object that responds to authenticate()';
23             }
24             }
25              
26             sub call {
27 26     26 1 158527 my($self, $env) = @_;
28 26         61 my $path = $env->{PATH_INFO};
29            
30 26 100       103 if( $env->{'psgix.session'}{remember} ){
31 6 100       21 if( $path ne '/logout' ){
32 5         59 $env->{'psgix.session.options'}{expires} = time + 60 * 60 * 24 * 30;
33             }
34             }
35              
36 26 100       87 if( $path eq '/login' ){
    100          
37 16         59 return $self->_login( $env );
38             }
39             elsif( $path eq '/logout' ){
40 3         21 return $self->_logout( $env );
41             }
42 7         29 return $self->app->( $env );
43             }
44              
45             sub _login {
46 16     16   29 my($self, $env) = @_;
47 16         17 my $login_error;
48 16 50 33     93 if( $self->secure
      66        
      33        
      33        
49             && ( !defined $env->{'psgi.url_scheme'} || lc $env->{'psgi.url_scheme'} ne 'https' )
50             && ( !defined $env->{HTTP_X_FORWARDED_PROTO} || lc $env->{HTTP_X_FORWARDED_PROTO} ne 'https' )
51             ){
52 1   33     31 my $server = $env->{HTTP_X_FORWARDED_FOR} || $env->{HTTP_X_HOST} || $env->{SERVER_NAME};
53 1 50       5 my $secure_url = "https://$server" . ( $self->ssl_port ? ':' . $self->ssl_port : '' ) . $env->{PATH_INFO};
54             return [
55 1         17 301,
56             [ Location => $secure_url ],
57             [ $self->_wrap_body( "Need a secure connection" ) ]
58             ];
59             }
60 15         302 my $params = Plack::Request->new( $env )->parameters;
61 15 100       4622 if( $env->{REQUEST_METHOD} eq 'POST' ){
    100          
62 9         16 my $user_id;
63 9         40 my $auth_result = $self->authenticator->( $params->get( 'username' ), $params->get( 'password' ), $env );
64 9         152 my $redir_to;
65 9 100       28 if( ref $auth_result ){
66 2         4 $login_error = $auth_result->{error};
67 2         3 $user_id = $auth_result->{user_id};
68 2         4 $redir_to = $auth_result->{redir_to};
69             }
70             else{
71 7 100       25 $login_error = 'Wrong username or password' if !$auth_result;
72 7         24 $user_id = $params->get( 'username' );
73 7         59 delete $env->{'psgix.session'}{user_id};
74 7         51 delete $env->{'psgix.session'}{remember};
75             }
76 9 100       28 if( !$login_error ){
77 6         18 $env->{'psgix.session.options'}->{change_id}++;
78 6         15 $env->{'psgix.session'}{user_id} = $user_id;
79 6 100       23 $env->{'psgix.session'}{remember} = ($params->get( 'remember' ) ? 1 : 0);
80 6         53 my $tmp_redir = delete $env->{'psgix.session'}{redir_to};
81 6 100       42 if( !defined($redir_to) ){
82 5         8 $redir_to = $tmp_redir;
83 5 50       37 $redir_to = '/' if
84             URI->new( $redir_to )->path eq $env->{PATH_INFO};
85             }
86             return [
87 6         5209 302,
88             [ Location => $redir_to ],
89             [ $self->_wrap_body( "Back" ) ]
90             ];
91             }
92             }
93             elsif( defined $env->{'psgix.session'}{user_id} ){
94             return [
95 1         7 200,
96             [ 'Content-Type' => 'text/html', ],
97             [ $self->_wrap_body( 'Already logged in' ) ]
98             ];
99             }
100 8   100     68 $env->{'psgix.session'}{redir_to} ||= $env->{HTTP_REFERER} || '/';
      66        
101 8         35 my $form = $self->_render_form(
102             username => $params->get( 'username' ),
103             login_error => $login_error,
104             redir_to => $env->{'psgix.session'}{redir_to},
105             );
106 8 100       35 if( $self->no_login_page ){
107 2         13 $env->{'Plack::Middleware::Auth::Form.LoginForm'} = $form;
108 2         19 return $self->app->( $env );
109             }
110             else{
111             return [
112 6         89 200,
113             [ 'Content-Type' => 'text/html', ],
114             [ $self->_wrap_body( "$form\nAfter login: $env->{'psgix.session'}{redir_to}" ) ]
115             ];
116             }
117             }
118              
119             sub _render_form {
120 8     8   61 my $self = shift;
121 8         31 my ( %params ) = @_;
122 8         16 my $out = '';
123 8 100       28 if( $params{login_error} ){
124 3         12 $out .= qq{
$params{login_error}
};
125             }
126 8 100       29 my $username = defined $params{username} ? $params{username} : '';
127 8         45 $out .= <
128            
129            
130            
131            
132            
133            
134            
135            
136             END
137 8         25 return $out;
138             }
139              
140             sub _logout {
141 3     3   9 my($self, $env) = @_;
142 3 50       15 if( $env->{REQUEST_METHOD} eq 'POST' ){
143 3         20 $self->_logout_hook( $env->{'psgix.session'}{user_id}, $env );
144 3         7 delete $env->{'psgix.session'}{user_id};
145 3         8 delete $env->{'psgix.session'}{remember};
146             }
147             return [
148 3   100     22 303,
149             [ Location => $self->after_logout || '/' ],
150             [ $self->_wrap_body( "Home") ]
151             ];
152             }
153              
154 3     3   6 sub _logout_hook {}
155              
156             sub _wrap_body {
157 13     13   45 my($self, $content) = @_;
158              
159 13         97 return "$content";
160             }
161              
162             1;
163              
164              
165              
166             =pod
167              
168             =head1 NAME
169              
170             Plack::Middleware::Auth::Form
171              
172             =head1 VERSION
173              
174             version 0.012
175              
176             =head1 SYNOPSIS
177              
178             builder {
179             enable 'Session';
180             enable 'Auth::Form', authenticator => \&check_pass;
181             \&my_app
182             }
183              
184             =head1 DESCRIPTION
185              
186             =over 4
187              
188             =item /login
189              
190             a page with a login form
191              
192             =item /logout
193              
194             logouts the user (only on a POST) and redirects him to C or C.
195              
196             =back
197              
198             After a succesful login the user is redirected back to url identified by
199             the C session parameter. You can set it using the psgix session
200             hash:
201              
202             $env->{'psgix.session'}{redir_to} = '/some/page';
203              
204             see L for more explanations.
205              
206             It also sets that session parameter from
207             C<< $env->{HTTP_REFERER} >> if it is not set or to C if even that is not available.
208             The username (or id) is saved to C session parameter, if you want
209             to save an id different from the username - then you need to return
210             a hashref from the C callback described below.
211              
212             If the login page looks too simplistic - the application can take over
213             displaying it by setting the C attribute. Then
214             the the login form will be saved to
215             C<< $env->{'Plack::Middleware::Auth::Form.LoginForm'} >>.
216              
217             =head1 CONFIGURATION
218              
219             =over 4
220              
221             =item authenticator
222              
223             A callback function that takes username and password supplied and
224             returns whether the authentication succeeds. Required.
225              
226             Authenticator can also be an object that responds to C
227             method that takes username and password and returns boolean, so
228             backends for L is perfect to use:
229              
230             use Authen::Simple::LDAP;
231             enable "Auth::Form", authenticator => Authen::Simple::LDAP->new(...);
232              
233             The callback can also return a hashref with three optional fields
234             C - the reason for the failure, C - the user id
235             to be saved in the session instead of the username, and C - a user
236             defined redirection page.
237              
238             =item no_login_page
239              
240             Save the login form on C<< $env->{'Plack::Middleware::Auth::Form.LoginForm'} >>
241             and let the
242             application display the login page (for a GET request).
243              
244             =item after_logout
245              
246             Where to go after logout, by default '/'.
247              
248             =item secure
249              
250             Make the login form redirect to https if requested with http.
251              
252             =item ssl_port
253              
254             The port for the https requests.
255              
256             =back
257              
258             =head1 SEE ALSO
259              
260             L
261             L
262              
263             =head1 ACKNOWLEDGEMENTS
264              
265             The C code and documentation copied from
266             L.
267              
268             =head1 CONTRIBUTORS
269              
270             Tokuhiro Matsuno
271             chromatic
272             hayajo
273             Kaare Rasmussen
274             Oliver Paukstadt
275             G. Paul Ziemba
276              
277             =head1 AUTHOR
278              
279             Zbigniew Lukasiak
280              
281             =head1 COPYRIGHT AND LICENSE
282              
283             This software is Copyright (c) 2011 by Zbigniew Lukasiak .
284              
285             This is free software, licensed under:
286              
287             The Artistic License 2.0 (GPL Compatible)
288              
289             =cut
290              
291              
292             __END__