File Coverage

blib/lib/Catalyst/Authentication/Credential/CAS.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Catalyst::Authentication::Credential::CAS;
2              
3              
4 4     4   4932364 use namespace::autoclean;
  4         14485  
  4         29  
5 4     4   1282 use Authen::CAS::Client;
  0            
  0            
6             use Moose;
7              
8             our $VERSION = '0.05';
9              
10              
11             has uri => ( is => 'ro', isa => 'Str', required => 1 );
12             has username_field => ( is => 'ro', isa => 'Str', default => 'username' );
13             has version => ( is => 'ro', isa => 'Str', default => '2.0' );
14              
15             has _cas => ( is => 'rw', isa => 'Authen::CAS::Client', lazy_build => 1 );
16              
17              
18             my %_version_map = (
19             '1.0' => '_authenticate_v10',
20             '2.0' => '_authenticate_v20',
21             );
22              
23              
24             sub BUILDARGS { $_[1] }
25              
26             sub _build__cas { Authen::CAS::Client->new( $_[0]->uri, fatal => 0 ) }
27              
28             sub authenticate {
29             my ( $self, $c, $realm, $authinfo ) = @_;
30              
31             # verify a supported version has been defined
32             my $_authenticate = $_version_map{ $self->version };
33             unless( defined $_authenticate ) {
34             $c->log->error( 'Unsupported CAS version v'. $self->version );
35             return;
36             }
37              
38             # derive a service URI if one is not provided
39             my $service = defined $authinfo->{service}
40             ? $authinfo->{service} : $c->uri_for( $c->action, $c->req->captures );
41              
42             # look for ticket in authinfo and then request parameters
43             my $ticket = defined $authinfo->{ticket}
44             ? $authinfo->{ticket} : $c->req->params->{ticket};
45              
46             # if no ticket was provided redirect to the CAS
47             unless( defined $ticket ) {
48             $c->log->debug( 'Redirecting to CAS for service "'. $service. '"' )
49             if $c->debug;
50              
51             $c->res->redirect( $self->_login_uri( $service, $authinfo ) );
52             die $Catalyst::DETACH;
53             }
54              
55             # validate ticket using authentication method defined in version map
56             $c->log->debug( 'Validating ticket "'. $ticket . '" for service "'. $service. '" using version v'. $self->version )
57             if $c->debug;
58             my $response = $self->$_authenticate( $service, $ticket, $authinfo );
59              
60             if( $response->is_error ) {
61             $c->log->error( 'CAS authentcation error: '. $response->error );
62             return;
63             }
64              
65             if( $response->is_failure ) {
66             $c->log->warn( 'CAS authentication failure: '. $response->code. ': '. $response->message );
67             return;
68             }
69              
70             $c->log->debug( 'Ticket validated for user "'. $response->user. '"' )
71             if $c->debug;
72              
73             $realm->find_user( { $self->username_field => $response->user }, $c )
74             }
75              
76             sub _login_uri {
77             my ( $self, $service, $params ) = @_;
78              
79             $self->_cas->login_url( $service,
80             map { exists $params->{$_} ? ( $_ => $params->{$_} ) : () }
81             qw( gateway renew ) )
82             }
83              
84             sub _authenticate_v10 {
85             my ( $self, $service, $ticket ) = @_;
86              
87             $self->_cas->validate( $service, $ticket )
88             }
89              
90             sub _authenticate_v20 {
91             my ( $self, $service, $ticket, $params ) = @_;
92              
93             $self->_cas->service_validate( $service, $ticket,
94             map { exists $params->{$_} ? ( $_ => $params->{$_} ) : () }
95             qw( pgtUrl renew ) )
96             }
97              
98              
99             __PACKAGE__->meta->make_immutable;
100              
101             1
102             __END__
103              
104             =head1 NAME
105              
106             Catalyst::Authentication::Credential::CAS - Catalyst support for JA-SIG's Central Authentication Service.
107              
108             =head1 SYNOPSIS
109              
110             # in MyApp.pm
111             __PACKAGE__->config->{'Plugin::Authentication'} = {
112             default_realm => 'default',
113             default => {
114             credential => {
115             class => 'CAS',
116             uri => 'https://cas.example.com/cas',
117             username_field => 'username', # optional
118             version => '2.0', # optional
119             },
120             store => {
121             ...
122             },
123             },
124             };
125              
126             # in a controller
127             sub auto :Private {
128             unless( $c->user_exists || $c->authenticate ) {
129             $c->res->status( 401 );
130             $c->res->body( 'Access Denied' );
131             return 0;
132             }
133             }
134              
135              
136             =head1 DESCRIPTION
137              
138             This module allows you to CAS-ify your Catalyst applications. It
139             integrates L<Authen::CAS::Client|Authen::CAS::Client> into Catalyst's
140             authentication framework.
141              
142             =head1 CONFIGURATION
143              
144             The following properties may be configured:
145              
146             =over 2
147              
148             =item B<uri>
149              
150             This specifies the base URI for the CAS instance and is passed to
151             the C<new()> method of the CAS client. See the documentation for
152             L<Authen::CAS::Client|Authen::CAS::Client> for more information.
153              
154             =item B<username_field>
155              
156             This specifies the name of the key in the C<$authinfo> hash that
157             is passed to C<$realm-E<gt>find_user()> for mapping the user name
158             returned from the CAS upon successful authentication and ticket
159             validation. Its value will depend on what the configured user
160             store expects. It defaults to C<'username'> if not specified in
161             the application's configuration.
162              
163             =item B<version>
164              
165             This specifies the verion of the CAS protocol to use. Currently
166             only C<'1.0'> and C<'2.0'> are supported. If not specified in
167             the application's configuration, the default of C<'2.0'> is used.
168             Its value will depend on if you can use the current version of
169             the CAS protocol or if you need to fall back to the older version
170             for compatibility.
171              
172             =back
173              
174             =head1 METHODS
175              
176             =over 2
177              
178             =item B<authenticate( $authinfo, $realm, $c )>
179              
180             This is called during the normal Catalyst authentication process
181             and should never be called directly.
182              
183             Since CAS is a service that verifies credentials outside of your
184             application, the login process for your application will have
185             two phases. In the first phase, an unauthenticated user will
186             attempt to access your application and be redirected to the CAS
187             for credential verification. A service URI must be provided to
188             the CAS so that once the user has been identified, they can be
189             redirected from the CAS back to your application for the second
190             phase of authentication. During this second phase the (supposedly)
191             authenticated user will be given a ticket that your application must
192             validate with the CAS. If the ticket is valid, the user is
193             considered authenticated. The C<authenticate()> method handles
194             both phases of authentication.
195              
196             Unless specified otherwise, this method will do its best to guess
197             the appropriate behavior for the service URI and ticket handling.
198             The service URI will be derived as the URI for the currently
199             executing action unless specified in the C<'service'> key of the
200             C<$authinfo> hash. The ticket returned from the CAS will be
201             retrieved from the request parameters unless specifed in the
202             C<'ticket'> key of the C<$authinfo> hash. If no ticket is
203             defined (phase one authentication) the response will be set to
204             redirect to the CAS and the current action will be detached.
205              
206             You may also pass other parameters in the C<$authinfo> hash that
207             will affect the way the CAS verifies credentials. See the
208             documentation for L<Authen::CAS::Client|Authen::CAS::Client> for
209             more on the C<'renew'>, C<'gateway'> and C<'pgtUrl'> parameters.
210              
211             =back
212              
213             =head1 BUGS
214              
215             None are known at this time, but if you find one, please feel
216             free to submit a report to the author.
217              
218             =head1 SEE ALSO
219              
220             =over 2
221              
222             =item L<Authen::CAS::Client|Authen::CAS::Client>
223              
224             =item L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
225              
226             =back
227              
228             =head1 AUTHOR
229              
230             jason hord E<lt>pravus@cpan.orgE<gt>
231              
232             with contributions from:
233              
234             Kevin L. Kane E<lt>kkane@cpan.orgE<gt>
235              
236             =head1 COPYRIGHT
237              
238             Copyright (c) 2010, jason hord
239              
240             Permission is hereby granted, free of charge, to any person obtaining a copy
241             of this software and associated documentation files (the "Software"), to deal
242             in the Software without restriction, including without limitation the rights
243             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
244             copies of the Software, and to permit persons to whom the Software is
245             furnished to do so, subject to the following conditions:
246              
247             The above copyright notice and this permission notice shall be included in
248             all copies or substantial portions of the Software.
249              
250             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
251             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
252             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
253             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
254             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
255             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
256             THE SOFTWARE.
257              
258             =cut