File Coverage

blib/lib/Catalyst/Authentication/Credential/RemoteHTTP.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Catalyst::Authentication::Credential::RemoteHTTP;
2              
3             # ABSTRACT: Authenticate against remote HTTP server
4              
5 1     1   30849 use strict;
  1         3  
  1         41  
6 1     1   5 use warnings;
  1         2  
  1         31  
7 1     1   447 use Moose;
  0            
  0            
8             use MooseX::Types::Moose qw/Object/;
9             use 5.008005;
10             use Catalyst::Exception ();
11             use Catalyst::Authentication::Credential::RemoteHTTP::UserAgent;
12             use namespace::autoclean;
13              
14             our $VERSION = '0.05'; # VERSION
15             our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY
16              
17             has realm => ( isa => Object, is => 'ro', required => 1 );
18              
19             has [qw/http_keep_alive defer_find_user/] => ( is => 'ro', default => 0 );
20             has username_field => ( is => 'ro', default => 'username' );
21             has password_field => ( is => 'ro', default => 'password' );
22              
23             has url => ( is => 'ro', required => 1 );
24              
25             has [qw/ user_prefix user_suffix /] => ( is => 'ro', default => '' );
26              
27             sub BUILDARGS {
28             my ( $class, $config, $app, $realm ) = @_;
29              
30             $config->{realm} = $realm;
31             $config->{app} = $app;
32             $config->{class} = $class;
33             return $config;
34             }
35              
36             sub authenticate {
37             my ( $self, $c, $realm, $authinfo ) = @_;
38              
39             my $username = $authinfo->{ $self->username_field };
40             unless ( defined($username) ) {
41             $c->log->debug("No username supplied")
42             if $c->debug;
43             return;
44             }
45             ## we remove the password_field before we pass it to the user
46             ## routine, as some store modules use all data passed to them
47             ## to find a matching user...
48             my $userfindauthinfo = { %{$authinfo} };
49             delete( $userfindauthinfo->{ $self->password_field } );
50              
51             my $user_obj;
52             $user_obj = $realm->find_user( $userfindauthinfo, $c )
53             unless ( $self->defer_find_user );
54              
55             if ( ref($user_obj) || $self->defer_find_user ) {
56             my $ua =
57             Catalyst::Authentication::Credential::RemoteHTTP::UserAgent->new(
58             keep_alive => $self->http_keep_alive ? 1 : 0 );
59              
60             # add prefix/suffix to user data to make auth_user, get password
61             my $auth_user = sprintf( '%s%s%s', $self->user_prefix, $username, $self->user_suffix );
62             my $password = $authinfo->{ $self->password_field };
63             $ua->set_credentials( $auth_user, $password );
64              
65             # do the request
66             my $res = $ua->head( $self->url );
67              
68             # did it succeed
69             if ( $res->is_success ) {
70              
71             # TODO: should we check here that it was actually authenticated?
72             # this could be done by ensuring there is a request chain...
73             $c->log->debug( "remote http auth succeeded for user " . $auth_user )
74             if $c->debug;
75             }
76             else {
77             $c->log->debug( "remote http auth FAILED for user " . $auth_user )
78             if $c->debug;
79             return;
80             }
81             }
82              
83             # get the user object now, if deferred before
84             $user_obj = $realm->find_user( $userfindauthinfo, $c )
85             if ( $self->defer_find_user );
86              
87             # deal with no-such-user in store
88             unless ( ref($user_obj) ) {
89             $c->log->debug("Unable to locate user matching user info provided")
90             if $c->debug;
91             return;
92             }
93             return $user_obj;
94             }
95              
96              
97             1; # End of Catalyst::Authentication::Credential::RemoteHTTP
98              
99             __END__
100             =pod
101              
102             =for stopwords ACKNOWLEDGEMENTS Daisuke Fixups LDAP Murase NTLM classname http ie linux url validator
103              
104             =head1 NAME
105              
106             Catalyst::Authentication::Credential::RemoteHTTP - Authenticate against remote HTTP server
107              
108             =head1 VERSION
109              
110             version 0.05
111              
112             =head1 SYNOPSIS
113              
114             package MyApp::Controller::Auth;
115              
116             use Catalyst qw/
117             Authentication
118             /;
119              
120             sub login : Local {
121             my ( $self, $c ) = @_;
122              
123             $c->authenticate( { username => $c->req->param('username'),
124             password => $c->req->param('password') });
125             }
126              
127             =head1 DESCRIPTION
128              
129             This authentication credential checker takes authentication
130             information (most often a username) and a password, and attempts to
131             validate the username and password provided against a remote http
132             server - ie against another web server.
133              
134             This is useful for environments where you want to have a single
135             source of authentication information, but are not able to
136             conveniently use a networked authentication mechanism such as LDAP.
137              
138             =head1 CONFIGURATION
139              
140             # example
141             __PACKAGE__->config(
142             'Plugin::Authentication' => {
143             default_realm => 'members',
144             realms => {
145             members => {
146             credential => {
147             class => 'RemoteHTTP',
148             url => 'http://intranet.company.com/authenticated.html',
149             password_field => 'password',
150             username_prefix => 'MYDOMAIN\\',
151             http_keep_alive => 1,
152             defer_find_user => 1,
153             },
154             ...
155             },
156             },
157             );
158              
159             =over 4
160              
161             =item class
162              
163             The classname used for Credential. This is part of
164             L<Catalyst::Plugin::Authentication> and is the method by which
165             Catalyst::Authentication::Credential::RemoteHTTP is loaded as the
166             credential validator. For this module to be used, this must be set to
167             'RemoteHTTP'.
168              
169             =item url
170              
171             The URL that is used to authenticate the user. The module attempts
172             to fetch this URL using a HEAD request (to prevent dragging a large
173             page across the network) with the credentials given. If this fails
174             then the authentication fails. If no URL is supplied in the config,
175             then an exception is thrown on startup.
176              
177             =item username_field
178              
179             The field in the authentication hash that contains the username.
180             This may vary, but is most likely 'username'. In fact, this is so
181             common that if this is left out of the config, it defaults to
182             'username'.
183              
184             =item password_field
185              
186             The field in the authentication hash that contains the password.
187             This may vary, but is most likely 'password'. In fact, this is so
188             common that if this is left out of the config, it defaults to
189             'password'.
190              
191             =item username_prefix
192              
193             This is an optional prefix to the username, which is added to the
194             username before it is used for authenticating to the remote http
195             server. It may be used (for example) to apply a domain to the
196             authenticated username.
197              
198             =item username_suffix
199              
200             This is an optional suffix to the username, which is added to the
201             username before it is used for authenticating to the remote http
202             server. It may be used (for example) to apply a domain to the
203             authenticated username.
204              
205             =item http_keep_alive
206              
207             If C<http_keep_alive> is set then keep_alive is set on the
208             connections to the remote http server. This is required if you are
209             using NTLM authentication (since an additional encryption nonce is
210             passed in the http negotiation). It is optional, but normally
211             harmless, for other forms of authentication.
212              
213             =item defer_find_user
214              
215             Normally the associated user store is queried for user information
216             before the remote http authentication takes place.
217              
218             However if, for example, you are using a
219             L<Catalyst::Authentication::Store::DBIx::Class> store with the
220             C<auto_create_user> option, then you can end up with invalid users
221             added to the store. If C<defer_find_user> is set true then the
222             remote http authentication occurs before the user is queried
223             against the store, ensuring that any users passed to the store are
224             known to be valid to the remote http server.
225              
226             =back
227              
228             =head1 METHODS
229              
230             There are no publicly exported routines in the RemoteHTTP module
231             (or indeed in most credential modules.) However, below is a
232             description of the routines required by
233             L<Catalyst::Plugin::Authentication> for all credential modules.
234              
235             =head2 new( $config, $app, $realm )
236              
237             Instantiate a new RemoteHTTP object using the configuration hash
238             provided in $config. A reference to the application is provided as
239             the second argument.
240              
241             =head2 authenticate( $authinfo, $c )
242              
243             Try to log a user in, receives a hashref containing authentication information
244             as the first argument, and the current context as the second.
245              
246             =head1 JUSTIFICATION
247              
248             Why would you use this module rather than one of the similar ones?
249              
250             This module gives a combination of authentication against a remote
251             http server, but maintains a local user store. This allows your
252             authentication to be delegated, but the authorization (for example
253             allocation and use of roles) to be determined by the local user
254             store.
255              
256             Nearly all the other alternatives require you to combine your
257             authentication and authorization databases.
258              
259             L<Catalyst::Authentication::Credential::HTTP::Proxy> has a similar
260             basis, but requires you to use HTTP basic authentication for the
261             application, which may not be appropriate.
262              
263             =head1 NTLM NOTES
264              
265             There are a number of issues relating to NTLM authentication. In
266             particular the supporting modules can be rather picky. To make NTLM
267             authentication work you must have an installed copy of libwww-perl
268             that includes L<LWP::Authen::Ntlm> (some linux distributions may drop
269             this component as it gives you additional dependency requirements over
270             the basic L<LWP> package).
271              
272             Additionally you require L<Authen::NTLM> of version 1.02 or later.
273             There are 2 different CPAN module distributions that provide this
274             module - but only one of them has the appropriate version number.
275              
276             Finally, if you are using L<NTLM-1.02> then you need to apply the
277             patch described in RT entry 9521
278             L<http://rt.cpan.org/Ticket/Display.html?id=9521>.
279              
280             When using NTLM authentication the configuration option
281             C<http_keep_alive> must be set true - otherwise the session to the
282             remote server is not maintained and the authentication nonce will
283             be lost between sessions.
284              
285             You may also need to set C<username_prefix> or C<username_suffix>
286             to set the correct domain for the authentication, unless the
287             username as given to your application includes the domain
288             information.
289              
290             =head1 ACKNOWLEDGEMENTS
291              
292             Daisuke Murase <typester@cpan.org> - original
293             L<Catalyst::Plugin::Authentication::Store::HTTP> used as the base
294             for a previous version of this module.
295              
296             The code framework was taken from
297             L<Catalyst::Authentication::Credential::Password>
298              
299             Tomas Doran (t0m) <t0m@state51.co.uk> - Fixups to best practice guidelines
300              
301             =head1 INSTALLATION
302              
303             See perlmodinstall for information and options on installing Perl modules.
304              
305             =head1 BUGS AND LIMITATIONS
306              
307             You can make new bug reports, and view existing ones, through the
308             web interface at L<http://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Authentication-Credential-RemoteHTTP>.
309              
310             =head1 AVAILABILITY
311              
312             The project homepage is L<https://metacpan.org/release/Catalyst-Authentication-Credential-RemoteHTTP>.
313              
314             The latest version of this module is available from the Comprehensive Perl
315             Archive Network (CPAN). Visit L<http://www.perl.com/CPAN/> to find a CPAN
316             site near you, or see L<https://metacpan.org/module/Catalyst::Authentication::Credential::RemoteHTTP/>.
317              
318             =head1 AUTHOR
319              
320             Nigel Metheringham <nigelm@cpan.org>
321              
322             =head1 COPYRIGHT AND LICENSE
323              
324             This software is copyright (c) 2012 by Nigel Metheringham <nigelm@cpan.org>.
325              
326             This is free software; you can redistribute it and/or modify it under
327             the same terms as the Perl 5 programming language system itself.
328              
329             =cut
330