590 lines
17 KiB
Perl
590 lines
17 KiB
Perl
package Lemonldap::Handlers::ValidateCAS;
|
|
use strict;
|
|
|
|
##### use ######
|
|
use Apache2::URI();
|
|
use Apache2::Const;
|
|
use Apache2::Connection;
|
|
use Apache2::ServerUtil ();
|
|
use MIME::Base64;
|
|
use LWP::UserAgent;
|
|
use Lemonldap::Handlers::MemsessionCAS;
|
|
use Lemonldap::Config::Parameters;
|
|
use Lemonldap::Config::Initparam;
|
|
use Lemonldap::Handlers::Utilities;
|
|
use Lemonldap::Handlers::CoreCAS;
|
|
use Apache2::Log();
|
|
use Apache2::ServerRec();
|
|
use CGI ':cgi-lib';
|
|
use CGI::Cookie;
|
|
use Crypt::CBC;
|
|
use URI::Escape;
|
|
use Template;
|
|
use Sys::Hostname;
|
|
|
|
#A retirer en prod
|
|
#use Data::Dumper;
|
|
#### common declaration #######
|
|
our ( @ISA, $VERSION, @EXPORTS );
|
|
$VERSION = '4.0.0';
|
|
our $VERSION_LEMONLDAP = "3.1.0";
|
|
our $VERSION_INTERNAL = "3.1.0";
|
|
|
|
####
|
|
####
|
|
#### my declaration #########
|
|
my %CONFIG;
|
|
my %CLIENT;
|
|
my $SAVE_MHURI;
|
|
my $NOM;
|
|
my $UA;
|
|
@ISA = qw(LWP::UserAgent );
|
|
my $ID_COLLECTED;
|
|
my $__STACK;
|
|
my %STACK;
|
|
my $ID_SAVE;
|
|
my $s = Apache2::ServerUtil->server;
|
|
|
|
### this anonymous function will be call when child dead , it will delete berkeley db file
|
|
my $cleanup = sub {
|
|
my $s = Apache2::ServerUtil->server;
|
|
my $srv_cfg = $s->dir_config;
|
|
|
|
my $vhosts = 0;
|
|
my $path_other;
|
|
for ( my $ser = $s->next ; $ser ; $ser = $ser->next ) {
|
|
$vhosts++;
|
|
$path_other = $ser->dir_config('cachedbpath') unless $path_other;
|
|
|
|
}
|
|
|
|
my $path = $srv_cfg->{'cachedbpath'} || $path_other;
|
|
unlink "$path/$$.db" if $path;
|
|
|
|
};
|
|
|
|
Apache2::ServerUtil->server->push_handlers( PerlChildExitHandler => $cleanup );
|
|
|
|
sub handler {
|
|
my $r = shift;
|
|
|
|
# URL des pages d'erreur a ne pas traiter
|
|
if ( $r->uri =~ /^\/LemonErrorPages/ ) {
|
|
return DECLINED;
|
|
}
|
|
########################
|
|
## log initialization
|
|
########################
|
|
my $log = $r->log;
|
|
my $messagelog;
|
|
my $cache2file;
|
|
my $APACHE_CODE;
|
|
|
|
# Url a ne pas traiter meme sans conf
|
|
# exemple
|
|
# PerlSetVar ExcludeRegex
|
|
#(?i)(\.smi|\.swf|\.vrml|\.ico|\.tif|\.gif|\.jpg|\.jpeg|\.js|\.css|\.jpeg|\.png|\.avi|ajaxaction|pngbehavior\.jsp)
|
|
|
|
my $regex = $r->dir_config('excluderegex');
|
|
if ( defined $regex ) {
|
|
$log->debug("REGEXP : $regex\n");
|
|
my $uri_input = $r->uri;
|
|
if ( $uri_input =~ /$regex/o ) {
|
|
$log->debug("$uri_input : EXCLUDED\n");
|
|
return DECLINED;
|
|
}
|
|
}
|
|
|
|
########################
|
|
## collect httpd param
|
|
########################
|
|
$ID_COLLECTED = '';
|
|
my $con = $r->dir_config();
|
|
|
|
$log->info($messagelog);
|
|
|
|
my $in_process = $r->dir_config('handlerid');
|
|
if ( $CONFIG{$in_process} ) {
|
|
$log->info(
|
|
"$CONFIG{$in_process}->{HANDLERID} XML $CONFIG{$in_process}->{XML} config already in use"
|
|
);
|
|
$ID_COLLECTED = $in_process;
|
|
}
|
|
else {
|
|
my $conf =
|
|
&Lemonldap::Config::Initparam::init_param_httpd( $log, $con );
|
|
|
|
#Domain insensible a la casse
|
|
$conf->{DOMAIN} = lc( $conf->{DOMAIN} );
|
|
|
|
#/Domain insensible a la casse
|
|
$ID_COLLECTED = $conf->{HANDLERID};
|
|
$CONFIG{$ID_COLLECTED} = $conf;
|
|
|
|
### I will try retieve HANDLERID from httpd conf
|
|
if ($ID_COLLECTED) {
|
|
$NOM = $ID_COLLECTED;
|
|
$messagelog =
|
|
"$NOM Phase : handler initialization LOAD HANDLERID httpd.conf:$CONFIG{$ID_COLLECTED}->{HANDLERID} : succeded";
|
|
}
|
|
else {
|
|
|
|
# I don't find anything for this handler in order to make link with XLM conf section
|
|
$messagelog =
|
|
"$NOM: Phase : handler initialization LOAD HANDLERID httpd.conf:failed";
|
|
}
|
|
$log->info($messagelog);
|
|
|
|
############################################
|
|
|
|
# my $ref = $CONFIG{$ID_COLLECTED}->{HANDLERID};
|
|
# $ref =~ s/\/.+// ;
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}: Phase : handler initialization LOAD XML file $CONFIG{$ID_COLLECTED}->{CONFIGFILE} and $CONFIG{$ID_COLLECTED}->{CONFIGDBPATH}"
|
|
);
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}: domain matched $CONFIG{$ID_COLLECTED}->{DOMAIN}"
|
|
);
|
|
if ( defined( $CONFIG{$ID_COLLECTED}->{CONFIGFILE} ) ) {
|
|
|
|
# my $ref = $CONFIG{$ID_COLLECTED}->{HANDLERID};
|
|
# $ref =~ s/\/.+// ;
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}: Phase : handler initialization LOAD XML file $CONFIG{$ID_COLLECTED}->{CONFIGFILE} and $CONFIG{$ID_COLLECTED}->{CONFIGDBPATH}"
|
|
);
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}: domain matched $CONFIG{$ID_COLLECTED}->{DOMAIN}"
|
|
);
|
|
$conf =
|
|
&Lemonldap::Config::Initparam::init_param_xml(
|
|
$CONFIG{$ID_COLLECTED} );
|
|
$log->info("$conf->{message}");
|
|
}
|
|
else {
|
|
$conf = {};
|
|
}
|
|
my $c =
|
|
&Lemonldap::Config::Initparam::merge( $CONFIG{$ID_COLLECTED}, $conf );
|
|
$CONFIG{$ID_COLLECTED} = $c;
|
|
|
|
# $CONFIG{$ID_COLLECTED}->{KEYIPC} .= "$$.db" if ($CONFIG{$ID_COLLECTED}->{KEYIPC});
|
|
|
|
}
|
|
|
|
#############################Test de CONFIG####################################
|
|
#################################################################################
|
|
|
|
## now I save the context of handler
|
|
$log->info("$CONFIG{$ID_COLLECTED}->{HANDLERID} end of initialization");
|
|
|
|
## addon for FASTPATTERNS
|
|
if ( ( $CONFIG{$ID_COLLECTED}->{FASTPATTERNS} )
|
|
&& !( $CONFIG{$ID_COLLECTED}->{ANONYMOUSFUNC} ) )
|
|
{
|
|
my $sub =
|
|
&Lemonldap::Config::Initparam::built_functionics(
|
|
$CONFIG{$ID_COLLECTED}->{FASTPATTERNS} );
|
|
$CONFIG{$ID_COLLECTED}->{ANONYMOUSFUNC_SRC} = $sub;
|
|
$CONFIG{$ID_COLLECTED}->{ANONYMOUSFUNC} = eval "$sub";
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}: Phase : FASTPATTERNS TABLE LOADED : $sub"
|
|
);
|
|
}
|
|
|
|
## addon for multihoming
|
|
if ( ( $CONFIG{$ID_COLLECTED}->{MULTIHOMING} )
|
|
&& !( $CONFIG{$ID_COLLECTED}->{SELECTOR} ) )
|
|
{
|
|
my $sub = $CONFIG{$ID_COLLECTED}->{SUB};
|
|
$CONFIG{$ID_COLLECTED}->{SELECTOR_SRC} = $sub;
|
|
$CONFIG{$ID_COLLECTED}->{SELECTOR} = eval "$sub";
|
|
}
|
|
|
|
################
|
|
|
|
##
|
|
foreach ( keys %{ $CONFIG{$ID_COLLECTED} } ) {
|
|
if ( $_ ne "ENCRYPTIONKEY" ) {
|
|
$log->info("$ID_COLLECTED:$_ => $CONFIG{$ID_COLLECTED}->{$_}");
|
|
}
|
|
}
|
|
|
|
##### end of initialization
|
|
##### begin process request
|
|
my $uri = $r->uri;
|
|
my %param = Vars();
|
|
my $ticket = $param{'ticket'};
|
|
my $service = $param{'service'};
|
|
( my $t_service ) = $service =~ /:\/\/(.+)/;
|
|
$t_service = "/" . $t_service;
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID} :service requested: $t_service");
|
|
|
|
#### multihoming
|
|
my $MHURI;
|
|
if ( $CONFIG{$ID_COLLECTED}->{MH} ) {
|
|
|
|
$MHURI = $CONFIG{$ID_COLLECTED}->{SELECTOR}->($t_service);
|
|
|
|
# Stop process if no multihosting
|
|
if ( ( $MHURI eq '1' ) || ( !($MHURI) ) ) {
|
|
$log->warn(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID} :multihoming failed for $t_service"
|
|
);
|
|
return DECLINED;
|
|
}
|
|
|
|
# load combo config
|
|
### I switch the context#
|
|
my $old_collected = $ID_COLLECTED;
|
|
$ID_COLLECTED = $MHURI;
|
|
|
|
$log->info(
|
|
"$CONFIG{$old_collected}->{HANDLERID} :SWITCH CONFIG $MHURI" );
|
|
if ( $CONFIG{$ID_COLLECTED}->{XML} ) {
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID} :MULTIHOMING already in use "
|
|
);
|
|
}
|
|
else {
|
|
my $c =
|
|
&Lemonldap::Config::Initparam::mergeMH( $CONFIG{$old_collected},
|
|
$MHURI );
|
|
$CONFIG{$ID_COLLECTED} = $c;
|
|
}
|
|
|
|
$log->info("$CONFIG{$ID_COLLECTED}->{HANDLERID} :MULTIHOMING ON");
|
|
}
|
|
|
|
#########################
|
|
##### for developper ###
|
|
if ( ( $uri =~ /_lemonldap_internal/i ) && ( $con->get('internaldebug') ) )
|
|
{
|
|
|
|
$r->content_type('text/html');
|
|
$r->print(<<END);
|
|
<html>
|
|
<head><title>lemonldap websso and CAS server</title></head>
|
|
<body>
|
|
<h1>Lemonldap websso ans CAS internal table</h1>
|
|
<p>
|
|
|
|
END
|
|
|
|
foreach ( keys %CONFIG ) {
|
|
$r->print("$_<br>\n");
|
|
}
|
|
$r->print("<hr>\n");
|
|
my $tmp = $CONFIG{$ID_COLLECTED};
|
|
$r->print("<h3>$ID_COLLECTED on $$</H3>\n");
|
|
|
|
foreach ( keys %$tmp ) {
|
|
if ( ref $tmp->{$_} ) {
|
|
my $t = ref $tmp->{$_};
|
|
$r->print("$_ => $t reference<br>\n");
|
|
}
|
|
else {
|
|
$r->print("$_ => $tmp->{$_}<br>\n");
|
|
}
|
|
|
|
}
|
|
$r->print("</body></html>");
|
|
|
|
return OK;
|
|
}
|
|
|
|
if ( ( $CONFIG{$ID_COLLECTED}->{FASTPATTERNS} )
|
|
&& ( $CONFIG{$ID_COLLECTED}->{ANONYMOUSFUNC}->($uri) eq 'OK' ) )
|
|
{
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID} :uri FASTPATTERNS matched: $uri"
|
|
);
|
|
return DECLINED;
|
|
}
|
|
$APACHE_CODE = DECLINED;
|
|
|
|
if ( $CONFIG{$ID_COLLECTED}->{ENABLELWP} ) {
|
|
$UA = __PACKAGE__->new;
|
|
$UA->agent( join "/", __PACKAGE__, $VERSION );
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}: Build-in proxy actived");
|
|
$r->handler("perl-script");
|
|
$r->push_handlers( PerlHandler => \&proxy_handler );
|
|
}
|
|
|
|
### before to enter in protected area
|
|
###
|
|
# return $APACHE_CODE if ( $CONFIG{$ID_COLLECTED}->{DISABLEACCESSCONTROL} );
|
|
|
|
### raz cache level 1
|
|
# is this area protected
|
|
# configuration check
|
|
#
|
|
|
|
# collect ticket and service
|
|
|
|
$ticket =~ s/ST-//;
|
|
|
|
( my $serveursession, my $principalsession ) =
|
|
Lemonldap::Handlers::MemsessionCAS->get(
|
|
'id' => $ticket,
|
|
'config' => $CONFIG{$ID_COLLECTED}
|
|
);
|
|
my $servicevalid = $serveursession->{service};
|
|
my $user = $serveursession->{username};
|
|
my $response;
|
|
my $ok;
|
|
if ( $servicevalid and $serveursession->{VALID} eq 'TRUE' ) {
|
|
$ok = 1;
|
|
}
|
|
|
|
if ( ( $CONFIG{$ID_COLLECTED}->{DISABLEACCESSCONTROL} ) and ( $ok == 1 ) ) {
|
|
|
|
$ok = 1;
|
|
}
|
|
else {
|
|
|
|
################ verif sur les habiliatation #############
|
|
$ok = 0;
|
|
my $controle = &Lemonldap::Handlers::CoreCAS::locationRules(
|
|
config => $CONFIG{$ID_COLLECTED},
|
|
session => $principalsession
|
|
);
|
|
if ( ( !$controle == 0 ) and ( defined $controle->{string} ) ) {
|
|
$ok = 1;
|
|
|
|
}
|
|
}
|
|
if ( $ok == 1 ) {
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}:$user -- $servicevalid ACCEPTED"
|
|
);
|
|
##### if cas 1 ####
|
|
if ( $uri !~ /validate/i ) {
|
|
$response = "yes\n";
|
|
$response .= "$user\n";
|
|
}
|
|
else { # cas v2
|
|
$response =
|
|
"<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
|
|
$response .= "\t<cas:authenticationSuccess>\n";
|
|
$response .= "\t\t<cas:user>$user</cas:user>\n";
|
|
$response .= "\t</cas:authenticationSuccess>\n";
|
|
$response .= "</cas:serviceResponse>\n";
|
|
}
|
|
|
|
$r->content_type('text/html');
|
|
$r->print;
|
|
$r->print($response);
|
|
|
|
}
|
|
else {
|
|
|
|
#delivrer reponse non
|
|
$log->info(
|
|
"$CONFIG{$ID_COLLECTED}->{HANDLERID}:$user -- $servicevalid REJETED"
|
|
);
|
|
|
|
if ( $uri !~ /validate/ ) {
|
|
$response = "no\n\n";
|
|
}
|
|
else { # cas v2
|
|
$response =
|
|
"<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
|
|
$response .=
|
|
"\t<cas:authenticationFailure code=\"INVALID_TICKET\">\n";
|
|
$response .= "\t\tTicket ST-$ticket not found\n";
|
|
$response .= "\t</cas:authenticationFailure>\n";
|
|
$response .= "</cas:serviceResponse>\n";
|
|
}
|
|
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
###########################################################
|
|
# _lemonldap_internal handler #
|
|
# add at your url /_lemonldap_internal #
|
|
# eg : http://appli1.demo.net/_lemonldap_internal #
|
|
# in order to dump the internal config of apache children #
|
|
# add ?id=handler append another config handler on output #
|
|
# #
|
|
###########################################################
|
|
|
|
############################
|
|
# #
|
|
# end _lemonldap_internal #
|
|
# handler #
|
|
# #
|
|
############################
|
|
|
|
1;
|
|
|
|
###############################
|
|
### fin etape 1
|
|
##############################
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
Lemonldap::Handlers::CAS - Perl extension for Lemonldap webSSO
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Lemonldap::Handlers::Validate ### Validate service ticket
|
|
use Lemonldap::Handlers::LoginCASFake ### Fake login : user must be egal to password (like CAS server demo)
|
|
use Lemonldap::Handlers::LogoutCAS ### logout SSO
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Lemonldap is a Reverse-proxy webSSO and CAS (Central Authentification Service) is an another websso from Yales university .
|
|
CAS acts like Authentification service NOT for authorization service .
|
|
|
|
These modules give the capacity at a lemonldap to become CAS server.
|
|
So , an user will be authenticate on CAS server AND on lemonldap.
|
|
Then the service ticket is send to serviceValidate the lemonldap can retrieve all session for user and process to authorization like a lemonldap .
|
|
|
|
=head1 Compatibility with CAS protocol.
|
|
|
|
Lemonldap manages those parameters :
|
|
|
|
=over 4
|
|
|
|
=item service
|
|
|
|
=item renew
|
|
|
|
=item gateway
|
|
|
|
=back
|
|
|
|
|
|
=head1 INSTALLATION
|
|
|
|
You must have an lemonldap websso installed (see doc on lemonldap.objectweb.org)
|
|
|
|
Configures your Apache like this :
|
|
|
|
<virtualhost 192.168.204.100>
|
|
servername authen.demo.net
|
|
loglevel debug
|
|
documentroot /usr/local/apache2/htdocs
|
|
alias /portal /usr/local/monapache/portal/
|
|
ErrorLog logs/error_log
|
|
<location /cas/login>
|
|
setHandler modperl
|
|
perlresponsehandler Lemonldap::Handlers::LoginCASFake
|
|
PerlSetVar Domain demo.net
|
|
PerlSetVar Configfile /usr/local/monapache/conf/application.xml
|
|
PerlOptions +GlobalRequest
|
|
</location>
|
|
<location /cas/serviceValidate>
|
|
setHandler modperl
|
|
perlresponsehandler Lemonldap::Handlers::ValidateCAS
|
|
PerlSetVar Domain demo.net
|
|
PerlSetVar HandlerID validate
|
|
PerlSetVar Configfile /usr/local/monapache/conf/application.xml
|
|
PerlOptions +GlobalRequest
|
|
</location>
|
|
<location /cas/logout>
|
|
setHandler modperl
|
|
perlresponsehandler Lemonldap::Handlers::LogoutCAS
|
|
PerlSetVar Domain demo.net
|
|
PerlSetVar Configfile /usr/local/monapache/conf/application.xml
|
|
PerlOptions +GlobalRequest
|
|
</location>
|
|
</virtualhost>
|
|
|
|
YOU CAN MIXED lemonldap handler et CAS server
|
|
|
|
Your application.xml is like this
|
|
<domain id="demo.net"
|
|
Cookie="lemondemo"
|
|
Sessionstore="memcached"
|
|
portal= "http://authen.demo.net/portail/accueil.pl"
|
|
LoginCASPage="/tmp/login.htmlcas"
|
|
LogoutCASPage="/tmp/logout.htmlcas"
|
|
LoginPage="/tmp/login.html"
|
|
ldap_server="192.168.247.30"
|
|
ldap_branch_people="ou=personnes,dc=demo,dc=net"
|
|
>
|
|
<handler
|
|
id="validate"
|
|
MultiHoming="pied,tete"
|
|
/>
|
|
<handler id="pied"
|
|
MotifIn="/192.168.204.108\/caspied"
|
|
applcode= "mail"
|
|
pluginpolicy="Lemonldap::Handlers::RowPolicy"
|
|
/>
|
|
<handler id="tete"
|
|
MotifIn="/192.168.204.108\/castete"
|
|
disableaccessControl="1"
|
|
/>
|
|
#### here normal lemonldap application #####
|
|
<handler
|
|
id="appli1"
|
|
applcode= "APT"
|
|
pluginpolicy="Lemonldap::Handlers::RowPolicy"
|
|
enableLWP="1"
|
|
basepub="http://myappli.demo.net"
|
|
basepriv="http://www.eyrolles.com"
|
|
>
|
|
</handler>
|
|
|
|
etc..
|
|
|
|
Put your login.html and logout.cas in the good directory (here /tmp) and the right name (here /tmp/login.htmlcas )
|
|
|
|
See the caspied and castete php examples (basic and standard CAS application)
|
|
|
|
=head1 NOTES
|
|
|
|
=over 4
|
|
|
|
=item Lemonldapcas is just an emulation of CAS server , use the real CAS server if you have only CAS application .
|
|
|
|
=item Lemonldap provides CAS version 1 and version 2 protocol ,if your location of validation contents the word 'Validate' (eg serviceValidation) the hanlder will use CAS version 2 overwise (eg service) it's CAS version 1
|
|
|
|
=item Lemonlap DOESN'T provide 'proxycas' service (in process) .
|
|
|
|
=item Lemonldap shares its sessions with other lemonldap (unlike CAS server) .
|
|
|
|
=item YOU MUST use HTTPS (by mod_ssl) in your apache server
|
|
|
|
=back
|
|
|
|
=head2 EXPORT
|
|
|
|
None by default.
|
|
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
Mention other useful documentation such as the documentation of
|
|
related modules or operating system documentation (such as man pages
|
|
in UNIX), or any relevant external documentation such as RFCs or
|
|
standards.
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
root, E<lt>germanlinux@yahoo.frE<gt>
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
Copyright (C) 2007 by germanlinux at yahoo.fr
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
it under the same terms as Perl itself, either Perl version 5.8.5 or,
|
|
at your option, any later version of Perl 5 you may have available.
|
|
|
|
|
|
=cut
|
|
|
|
|