File Coverage

blib/lib/Apache2/AuthCASSimple.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 Apache2::AuthCASSimple;
2              
3 1     1   21379 use strict;
  1         2  
  1         37  
4 1     1   5 use warnings;
  1         2  
  1         31  
5 1     1   460 use Apache2::Const qw( OK AUTH_REQUIRED DECLINED REDIRECT SERVER_ERROR M_GET);
  0            
  0            
6             use Apache2::RequestUtil ();
7             use Apache2::RequestRec ();
8             use Apache2::Log;
9             use Apache::Session::Wrapper;
10             use Authen::CAS::Client;
11             use Apache2::Connection;
12             use Apache2::RequestIO;
13             use URI::Escape;
14             use vars qw($VERSION);
15              
16             $VERSION = '0.10';
17              
18             #
19             # handler()
20             #
21             # Called by apache/mod_perl
22             #
23             sub handler ($) {
24             my $r = shift;
25             my $log = $r->log();
26              
27              
28             # does it need to do something ?
29             #return DECLINED unless($r->ap_auth_type() eq __PACKAGE__);
30              
31             $log->info(__PACKAGE__.": == Entering into authentification process.:" );
32             $log->info(__PACKAGE__.": == ".$r->method.' '.$r->uri() .' '.$r->args() );
33             $log->info(__PACKAGE__.": == ".$r->connection->remote_ip() );
34              
35             # Get module config (Apache Perl SetVAR values)
36             my $cas_session_timeout = $r->dir_config('CASSessionTimeout') || 60;
37             my $cas_ssl = $r->dir_config('CASServerNoSSL')?0:1;
38             my $cas_name = $r->dir_config('CASServerName') || 'my.casserver.com';
39             my $cas_port = $r->dir_config('CASServerPort') ? ':'.$r->dir_config('CASServerPort') : ':443' ;
40             $cas_port = '' if ( $cas_port eq ':443' && $cas_ssl );
41             my $cas_path = $r->dir_config('CASServerPath') || '/' ;
42             $cas_path = '' if ($cas_path eq '/');
43             my $mod_proxy = $r->dir_config('ModProxy');
44              
45             # Check for internal session
46             my $user;
47             if($cas_session_timeout >= 0 && ($user = _get_user_from_session($r))) {
48             $log->info(__PACKAGE__.": Session found for user $user.");
49             $r->user($user);
50             return OK;
51             }
52             elsif($cas_session_timeout >= 0) {
53             $log->info(__PACKAGE__.": No session found.");
54             }
55             else {
56             $log->info(__PACKAGE__.": Session disabled.");
57             }
58              
59             # instance CAS object
60             my ($cas, %options);
61             $options{casUrl} = ($cas_ssl ? 'https://' : 'http://').$cas_name.$cas_port.$cas_path;
62             # $log->info('==casUrl==='.$options{casUrl}.'____');
63             # $options{CAFile} = $cfg->{_ca_file} if ($cfg->{_cas_ssl});
64              
65             unless($cas = Authen::CAS::Client->new($options{casUrl}, fatal => 1)) {
66             $log->error(__PACKAGE__.": Unable to create CAS instance.");
67             return SERVER_ERROR;
68             }
69              
70             my $requested_url = _get_requested_url($r,$mod_proxy);
71             my $login_url = uri_escape $requested_url;
72             $login_url = $cas->login_url().$login_url;
73             #$log->info( '==login_url==='.$login_url.'____');
74              
75             my %args = map { split '=', $_ } split '&', $r->args();
76             my $ticket = $args{'ticket'};
77             # redirect to CAS server unless ticket parameter
78             unless ($ticket) {
79             $log->info(__PACKAGE__.": No ticket, client redirected to CAS server. ".$login_url);
80             $r->headers_out->add("Location" => $login_url);
81             return REDIRECT;
82             }
83              
84              
85             # Validate the ticket we received
86             if ($ticket=~/^PT/) {
87             my $r = $cas->proxy_validate( $requested_url, $ticket );
88             if( $r->is_success() ) {
89             $user=$r->user();
90             $log->info(__PACKAGE__.": Validate PT on CAS Proxy server. ".join ",", $r->proxies());
91             };
92             }
93             else {
94             $log->info(__PACKAGE__.": Validate ST $ticket on CAS Proxy server : $requested_url");
95             my $r = $cas->service_validate( $requested_url, $ticket );
96             if ( $r->is_success() ) {
97             $user = $r->user();
98             }
99             }
100              
101             unless ($user) {
102             $log->info(__PACKAGE__.": Unable to validate ticket ".$ticket." on CAS server.");
103             $r->err_headers_out->add("Location" => $r->uri._str_args($r)); # remove ticket
104             return REDIRECT;
105             }
106              
107             $log->info(__PACKAGE__.": Ticket ".$ticket." succesfully validated for $user");
108              
109             if ( $user ) {
110             $r->user($user);
111             my $str_args = _str_args($r); # remove ticket
112              
113             $log->info(__PACKAGE__.": New session ".$r->uri() ."--".$r->args());
114              
115             # if we are there (and timeout is set), we can create session data and cookie
116             _create_user_session($r) if($cas_session_timeout >= 0);
117             $log->debug("Location => ".$r->uri . ($str_args ? '?' . $str_args : ''));
118             $r->err_headers_out->add("Location" => $r->uri . ($str_args ? '?' . $str_args : '') );
119              
120             # if session, redirect remove ticket in url
121             return ($cas_session_timeout >= 0)?REDIRECT:OK;
122             }
123              
124             return DECLINED;
125              
126             }
127              
128             #
129             # _get_args
130             #
131             # Stringify args
132             #
133              
134             sub _str_args ($;$) {
135             my $r = shift;
136             my $keep_ticket = shift;
137              
138             my %args = map { split '=', $_ } split '&', $r->args();
139             my @qs = ();
140              
141             foreach (sort {$a cmp $b} keys(%args)) {
142             next if ($_ eq 'ticket' && !$keep_ticket);
143             my $str = $args{$_};
144             push(@qs, $_."=".$str);
145             }
146              
147             my $str_args = join("\&", @qs);
148             return $str_args;
149             }
150              
151              
152             #
153             # _get_requested_url()
154             #
155             # Return the URL requested by client (with args)
156             #
157             sub _get_requested_url ($$) {
158             my $r = shift;
159             my $mod_proxy = shift;
160             my $is_https = $r->dir_config('HTTPSServer') || 0;
161              
162             my $port = $r->get_server_port();
163              
164             my $url = $is_https ? 'https://' : 'http://';
165             $url .= $r->hostname();
166             $url .= ':'.$port if (!$mod_proxy && ( ($is_https && $port != 443) || (!$is_https && $port != 80) ));
167             $url .= $r->uri()._get_query_string($r);
168              
169             return $url;
170             }
171              
172             #
173             # _get_query_string()
174             #
175             # Return the query string
176             #
177             sub _get_query_string ($) {
178             my $r = shift;
179              
180             _post_to_get($r) if ($r->method eq 'POST');
181              
182             my $str_args = _str_args($r);
183             return ($str_args)?"?".$str_args:'';
184             }
185              
186             #
187             # _post_to_get()
188             #
189             # Convert POST data to GET
190             #
191             sub _post_to_get ($) {
192             my $r = shift;
193              
194             my $content;
195             $r->read($content,$r->headers_in->{'Content-length'});
196              
197             $r->log()->info('POST to GET: '.$content);
198             $r->method("GET");
199             $r->method_number(M_GET);
200             $r->headers_in->unset("Content-length");
201             $r->args($content);
202             }
203              
204             #
205             # _remove_ticket
206             #
207             # Remove ticket from query string arguments
208             #
209             sub _remove_ticket ($) {
210             my $r = shift;
211             $r->args( _str_args($r));
212             }
213              
214             #
215             # _get_user_from_session()
216             #
217             # Retrieve username if a session exist ans is correctly filled
218             #
219             sub _get_user_from_session ($) {
220             my $r = shift;
221             my $s;
222              
223             my $mod_proxy = $r->dir_config('ModProxy');
224             my $cas_session_dir = $r->dir_config('CASSessionDirectory') || '/tmp';
225             my $cas_cookie_path = $r->dir_config('CASFixDirectory') || '/';
226             my $cas_session_timeout = $r->dir_config('CASSessionTimeout') || 60;
227             my $is_https = $r->dir_config('HTTPSServer') || 0;
228              
229             $r->log()->info(__PACKAGE__.": Checking session.");
230              
231             eval { $s = Apache::Session::Wrapper->new(
232             class => 'File',
233             directory => $cas_session_dir,
234             lock_directory => $cas_session_dir,
235             use_cookie => 1,
236             cookie_secure => $is_https,
237             cookie_resend => 1,
238             cookie_expires => 'session',
239             cookie_path => $cas_cookie_path
240             );
241            
242             $r->log()->info(__PACKAGE__.": Session id ".$s->{session_id});
243            
244             };
245              
246             return "" unless(defined $s);
247            
248             my $ip = ($mod_proxy)?$r->headers_in->{'X-Forwarded-For'}:$r->connection->remote_ip();
249             my $user = $s->session->{'CASUser'} || 'empty cookie';
250              
251             my $session_time = $s->session->{'time'} || 0;
252              
253             if ($cas_session_timeout && $session_time + $cas_session_timeout < time) {
254             $r->log()->warn(__PACKAGE__.': Session TimeOut, for '.$s->{session_id}.' / '.$ip );
255             $s->delete_session();
256             return "";
257             };
258              
259              
260             if($s->session->{'CASIP'} ne $ip) {
261             $r->log()->info(__PACKAGE__.": Remote IP Address changed along requests !");
262             $s->delete_session();
263             return "";
264             }
265             elsif( $user ) {
266             return $user;
267             }
268             else {
269             $r->log()->info(__PACKAGE__.": Session found, but no data inside it.");
270             $s->delete_session();
271             return "";
272             }
273             }
274              
275             #
276             # _create_user_session()
277             #
278             # Create a user session and send cookie
279             #
280             sub _create_user_session ($) {
281             my $r = shift;
282              
283             my $mod_proxy = $r->dir_config('ModProxy');
284             my $cas_session_dir = $r->dir_config('CASSessionDirectory') || '/tmp';
285             my $cas_cookie_path = $r->dir_config('CASFixDirectory') || '/';
286             my $is_https = $r->dir_config('HTTPSServer') || 0;
287              
288             $r->log()->info(__PACKAGE__.": Creating session for ".$r->user());
289              
290             my $s = Apache::Session::Wrapper->new(
291             class => 'File',
292             directory => $cas_session_dir,
293             lock_directory => $cas_session_dir,
294             use_cookie => 1,
295             cookie_secure => $is_https,
296             cookie_resend => 1,
297             cookie_expires => 'session',
298             cookie_path => $cas_cookie_path
299             );
300              
301             unless ($s) {
302             $r->log()->info(__PACKAGE__.": Unable to create session for ".$r->connection->user().".");
303             return;
304             }
305              
306             $r->log()->info(__PACKAGE__.": Session id ".$s->{session_id});
307              
308             $s->session->{'CASUser'} = $r->user();
309             my $ip = ($mod_proxy)?$r->headers_in->{'X-Forwarded-For'}:$r->connection->remote_ip();
310             $s->session->{'CASIP'} = $ip;
311             $s->session->{'time'} = time();
312              
313             };
314              
315              
316             1;
317              
318             __END__