Apache2-AuthCASSimple
view release on metacpan or search on metacpan
lib/Apache2/AuthCASSimple.pm view on Meta::CPAN
package Apache2::AuthCASSimple;
use strict;
use warnings;
use Apache2::Const qw( OK AUTH_REQUIRED DECLINED REDIRECT SERVER_ERROR M_GET);
use Apache2::RequestUtil ();
use Apache2::RequestRec ();
use Apache2::Log;
use Apache::Session::Wrapper;
use Authen::CAS::Client;
use Apache2::Connection;
use Apache2::RequestIO;
use URI::Escape;
use vars qw($VERSION);
$VERSION = '0.10';
#
# handler()
#
# Called by apache/mod_perl
#
sub handler ($) {
my $r = shift;
my $log = $r->log();
# does it need to do something ?
#return DECLINED unless($r->ap_auth_type() eq __PACKAGE__);
$log->info(__PACKAGE__.": == Entering into authentification process.:" );
$log->info(__PACKAGE__.": == ".$r->method.' '.$r->uri() .' '.$r->args() );
$log->info(__PACKAGE__.": == ".$r->connection->remote_ip() );
# Get module config (Apache Perl SetVAR values)
my $cas_session_timeout = $r->dir_config('CASSessionTimeout') || 60;
my $cas_ssl = $r->dir_config('CASServerNoSSL')?0:1;
my $cas_name = $r->dir_config('CASServerName') || 'my.casserver.com';
my $cas_port = $r->dir_config('CASServerPort') ? ':'.$r->dir_config('CASServerPort') : ':443' ;
$cas_port = '' if ( $cas_port eq ':443' && $cas_ssl );
my $cas_path = $r->dir_config('CASServerPath') || '/' ;
$cas_path = '' if ($cas_path eq '/');
my $mod_proxy = $r->dir_config('ModProxy');
# Check for internal session
my $user;
if($cas_session_timeout >= 0 && ($user = _get_user_from_session($r))) {
$log->info(__PACKAGE__.": Session found for user $user.");
$r->user($user);
return OK;
}
elsif($cas_session_timeout >= 0) {
$log->info(__PACKAGE__.": No session found.");
}
else {
$log->info(__PACKAGE__.": Session disabled.");
}
# instance CAS object
my ($cas, %options);
$options{casUrl} = ($cas_ssl ? 'https://' : 'http://').$cas_name.$cas_port.$cas_path;
# $log->info('==casUrl==='.$options{casUrl}.'____');
# $options{CAFile} = $cfg->{_ca_file} if ($cfg->{_cas_ssl});
unless($cas = Authen::CAS::Client->new($options{casUrl}, fatal => 1)) {
$log->error(__PACKAGE__.": Unable to create CAS instance.");
return SERVER_ERROR;
}
my $requested_url = _get_requested_url($r,$mod_proxy);
my $login_url = uri_escape $requested_url;
$login_url = $cas->login_url().$login_url;
#$log->info( '==login_url==='.$login_url.'____');
my %args = map { split '=', $_ } split '&', $r->args();
my $ticket = $args{'ticket'};
# redirect to CAS server unless ticket parameter
unless ($ticket) {
$log->info(__PACKAGE__.": No ticket, client redirected to CAS server. ".$login_url);
$r->headers_out->add("Location" => $login_url);
return REDIRECT;
}
# Validate the ticket we received
if ($ticket=~/^PT/) {
my $r = $cas->proxy_validate( $requested_url, $ticket );
if( $r->is_success() ) {
$user=$r->user();
$log->info(__PACKAGE__.": Validate PT on CAS Proxy server. ".join ",", $r->proxies());
};
}
else {
$log->info(__PACKAGE__.": Validate ST $ticket on CAS Proxy server : $requested_url");
my $r = $cas->service_validate( $requested_url, $ticket );
if ( $r->is_success() ) {
lib/Apache2/AuthCASSimple.pm view on Meta::CPAN
$url .= ':'.$port if (!$mod_proxy && ( ($is_https && $port != 443) || (!$is_https && $port != 80) ));
$url .= $r->uri()._get_query_string($r);
return $url;
}
#
# _get_query_string()
#
# Return the query string
#
sub _get_query_string ($) {
my $r = shift;
_post_to_get($r) if ($r->method eq 'POST');
my $str_args = _str_args($r);
return ($str_args)?"?".$str_args:'';
}
#
# _post_to_get()
#
# Convert POST data to GET
#
sub _post_to_get ($) {
my $r = shift;
my $content;
$r->read($content,$r->headers_in->{'Content-length'});
$r->log()->info('POST to GET: '.$content);
$r->method("GET");
$r->method_number(M_GET);
$r->headers_in->unset("Content-length");
$r->args($content);
}
#
# _remove_ticket
#
# Remove ticket from query string arguments
#
sub _remove_ticket ($) {
my $r = shift;
$r->args( _str_args($r));
}
#
# _get_user_from_session()
#
# Retrieve username if a session exist ans is correctly filled
#
sub _get_user_from_session ($) {
my $r = shift;
my $s;
my $mod_proxy = $r->dir_config('ModProxy');
my $cas_session_dir = $r->dir_config('CASSessionDirectory') || '/tmp';
my $cas_cookie_path = $r->dir_config('CASFixDirectory') || '/';
my $cas_session_timeout = $r->dir_config('CASSessionTimeout') || 60;
my $is_https = $r->dir_config('HTTPSServer') || 0;
$r->log()->info(__PACKAGE__.": Checking session.");
eval { $s = Apache::Session::Wrapper->new(
class => 'File',
directory => $cas_session_dir,
lock_directory => $cas_session_dir,
use_cookie => 1,
cookie_secure => $is_https,
cookie_resend => 1,
cookie_expires => 'session',
cookie_path => $cas_cookie_path
);
$r->log()->info(__PACKAGE__.": Session id ".$s->{session_id});
};
return "" unless(defined $s);
my $ip = ($mod_proxy)?$r->headers_in->{'X-Forwarded-For'}:$r->connection->remote_ip();
my $user = $s->session->{'CASUser'} || 'empty cookie';
my $session_time = $s->session->{'time'} || 0;
if ($cas_session_timeout && $session_time + $cas_session_timeout < time) {
$r->log()->warn(__PACKAGE__.': Session TimeOut, for '.$s->{session_id}.' / '.$ip );
$s->delete_session();
return "";
};
if($s->session->{'CASIP'} ne $ip) {
$r->log()->info(__PACKAGE__.": Remote IP Address changed along requests !");
$s->delete_session();
return "";
}
elsif( $user ) {
return $user;
}
else {
$r->log()->info(__PACKAGE__.": Session found, but no data inside it.");
$s->delete_session();
return "";
}
}
#
# _create_user_session()
#
# Create a user session and send cookie
#
sub _create_user_session ($) {
my $r = shift;
my $mod_proxy = $r->dir_config('ModProxy');
my $cas_session_dir = $r->dir_config('CASSessionDirectory') || '/tmp';
my $cas_cookie_path = $r->dir_config('CASFixDirectory') || '/';
my $is_https = $r->dir_config('HTTPSServer') || 0;
$r->log()->info(__PACKAGE__.": Creating session for ".$r->user());
my $s = Apache::Session::Wrapper->new(
class => 'File',
directory => $cas_session_dir,
lock_directory => $cas_session_dir,
use_cookie => 1,
cookie_secure => $is_https,
cookie_resend => 1,
cookie_expires => 'session',
cookie_path => $cas_cookie_path
);
unless ($s) {
$r->log()->info(__PACKAGE__.": Unable to create session for ".$r->connection->user().".");
return;
}
$r->log()->info(__PACKAGE__.": Session id ".$s->{session_id});
$s->session->{'CASUser'} = $r->user();
my $ip = ($mod_proxy)?$r->headers_in->{'X-Forwarded-For'}:$r->connection->remote_ip();
$s->session->{'CASIP'} = $ip;
$s->session->{'time'} = time();
};
1;
__END__
=head1 NAME
Apache2::AuthCASSimple - Apache2 module to authentificate through a CAS server
=head1 DESCRIPTION
Apache2::AuthCASSimple is an authentication module for Apache2/mod_perl2. It allow you to authentificate users through a Yale CAS server. It means you don't need to give login/password if you've already be authentificate by the CAS server, only ticke...
This module allow the use of simple text files for sessions.
=head1 SYNOPSIS
PerlOptions +GlobalRequest
<Location /protected>
AuthType Apache2::AuthCASSimple
PerlAuthenHandler Apache2::AuthCASSimple
PerlSetVar CASServerName my.casserver.com
PerlSetVar CASServerPath /
# PerlSetVar CASServerPort 443
# PerlSetVar CASServerNoSSL 1
PerlSetVar CASSessionTimeout 3660
PerlSetVar CASSessionDirectory /tmp
# PerlSetVar CASFixDirectory /
# PerlSetVar ModProxy 1
# PerlSetVar HTTPSServer 1
require valid-user
</Location>
or
order deny,allow
deny from all
require user xxx yyyy
satisfy any
=head1 CONFIGURATION
=over 4
=item CASServerName
Name of the CAS server. It can be a numeric IP address.
=item CASServerPort
Port of the CAS server. Default is 443.
=item CASServerPath
Path (URI) of the CAS server. Default is "/cas".
=item CASServerNoSSL
Disable SSL transaction wih CAS server (HTTPS). Default is off.
=item CASCaFile
CAS server public key. This file is used to allow secure connection
between the webserver using Apache2::AuthCASSimple and the CAS server.
DEPRECATED : L<Authen::CAS::Client> use L<LWP::UserAgent> to make https requests
=item CASSessionTimeout
Timeout (in second) for session create by Apache2::AuthCASSimple (to avoid CAS server overloading). Default is 60.
-1 means disable.
0 mean infinite (until the user close browser).
=item CASSessionDirectory
Directory where session data are stored. Default is /tmp.
=item CASFixDirectory
Force the path of the session cookie for same policy in all subdirectories else current directory is used.
=item ModProxy
Apache2 mod_perl2 don't be use with mod_proxy. Default is off.
=item HTTPSServer
If you want to keep a HTTPS server for all data. Default is 0.
=item OK AUTH_REQUIRED DECLINED REDIRECT SERVER_ERROR M_GET
Apache constants to make pod coverage happy
=back
=head1 METHOD
=head2 handler
call by apache2
=head1 VERSION
This documentation describes Apache2::AuthCASSimple version 0.10
=head1 BUGS AND TROUBLESHOOTING
=over 4
=item *
Old expired sessions files must be deleted with an example provided script : C<delete_session_data.pl>
=item *
L<Apache::Session::Wrapper> certainly need L<Apache2::Cookie>
=item *
C<$r> must be global for sessions with L<Apache::Session::Wrapper>, add
PerlOptions +GlobalRequest
in your virtualhost conf
=item *
Apreq module must be enable in debian
a2enmod apreq
or add
( run in 1.523 second using v1.01-cache-2.11-cpan-140bd7fdf52 )