Apache2-AuthCAS
view release on metacpan or search on metacpan
lib/Apache2/AuthCAS.pm view on Meta::CPAN
# Apache2::AuthCAS
# Jason Hitt, March 2007
#
# Apache auth module to protect underlying resources using JA-SIG's Central
# Authentication Service
package Apache2::AuthCAS;
$Apache2::AuthCAS::VERSION = "0.4";
use strict;
use warnings FATAL => 'all';
use Apache2::RequestRec ();
use Apache2::RequestIO ();
use Apache2::RequestUtil ();
#use Apache2::ServerRec ();
use Apache2::Module ();
use Apache2::URI ();
use Apache2::Const -compile => qw(FORBIDDEN HTTP_MOVED_TEMPORARILY OK DECLINED HTTP_OK :log);
use mod_perl2;
use vars qw($INITIALIZED $SESSION_CLEANUP_COUNTER);
use APR::URI;
use Apache2::Log;
use Net::SSLeay;
use MIME::Base64;
use DBI;
use URI::Escape;
use XML::Simple;
# logging flags
my $LOG_ERROR = 0;
my $LOG_WARN = 1;
my $LOG_INFO = 2;
my $LOG_DEBUG = 3;
my $LOG_EMERG = 4;
my %ERROR_CODES = (
"DB" => "Database Service Error",
"PGT" => "CAS Proxy Service Error",
"PGT_RECEPTOR" => "Proxy Receptor Error",
"INVALID_RESPONSE" => "Invalid Service Response",
"INVALID_PGT" => "Invalid Proxy Granting Ticket",
"MISSING_PGT" => "Missing Proxy Granting Ticket",
"CAS_CONNECT" => "CAS couldn't validate service ticket",
);
my %DEFAULTS = (
"Host" => "localhost",
"Port" => "443",
"LoginUri" => "/cas/login",
"LogoutUri" => "/cas/logout",
"ProxyUri" => "/cas/proxy",
"ProxyValidateUri" => "/cas/proxyValidate",
"ServiceValidateUri" => "/cas/serviceValidate",
"LogLevel" => 0,
"PretendBasicAuth" => 0,
"Service" => undef,
"ProxyService" => undef,
"ErrorUrl" => "http://localhost/cas/error/",
"SessionCleanupThreshold" => 10,
"SessionCookieName" => "APACHECAS",
"SessionCookieDomain" => undef,
"SessionCookieSecure" => 0,
"SessionTimeout" => 1800,
"RemoveTicket" => 0,
"NumProxyTickets" => 0,
"DbDriver" => "Pg",
"DbDataSource" => "dbname=apache_cas;host=localhost;port=5432",
"DbSessionTable" => "cas_sessions",
"DbUser" => "cas",
"DbPass" => "cas",
);
# default to 0
$SESSION_CLEANUP_COUNTER = 0 if (!defined($SESSION_CLEANUP_COUNTER));
sub dbConnect($)
{
my($self) = @_;
my $dbh = DBI->connect(
"dbi:" . $self->casConfig("DbDriver")
. ":" . $self->casConfig("DbDataSource"),
$self->casConfig("DbUser"), $self->casConfig("DbPass"),
{ AutoCommit => 1 }
);
if (!defined($dbh))
{
$self->logMsg("db connect error: $DBI::errstr");
return undef;
}
return $dbh;
}
sub getApacheConfig($)
{
my($self) = @_;
$self->{'casConfig'} = Apache2::Module::get_config('Apache2::AuthCAS::Configuration'
, $self->{'request'}->server
, $self->{'request'}->per_dir_config);
lib/Apache2/AuthCAS.pm view on Meta::CPAN
$self->logMsg("deleting session mapping for service_ticket='$delete_service_ticket'", $LOG_DEBUG);
my $dbh = $self->dbConnect() or return 0;
$dbh->do("DELETE FROM " . $self->casConfig("DbSessionTable")
. " WHERE service_ticket= ?", undef, $delete_service_ticket
);
if ($dbh->err)
{
$self->logMsg("error deleting session mapping for service_ticket='$delete_service_ticket' ($DBI::errstr)", $LOG_DEBUG);
}
}
# perform any cleanup that is needed
$self->cleanup();
# see if any of our other handlers have specified that they have already
# sufficiently checked the authenticating user
my $authenticated = $r->subprocess_env->{'AUTHENTICATED'} || "";
$self->logMsg("authenticated='$authenticated'", $LOG_DEBUG);
return (Apache2::Const::OK) if ($authenticated eq "true");
# Parse the query string to get the ticket, plus any GET variables
# to rebuild our service string (which is needed for CAS to send the
# client back to the originating service).
my %params = $self->parse_query_parameters($uri->query);
# Check for a proxy receptor call
if ($params{'pgt'} and $params{'pgtIou'})
{
return $self->proxy_receptor($params{'pgtIou'}, $params{'pgt'});
}
# Check for a session cookie
if (my $cookie = $r->headers_in->{'Cookie'})
{
# we have a session cookie, so we need to get the session id
$self->logMsg("cookie found: '$cookie'", $LOG_DEBUG);
# get session id from the cookie
my $cookieName = $self->casConfig("SessionCookieName");
$cookie =~ /.*$cookieName=([^;]+)(\s*;.*|\s*$)/;
my $sid = $1;
$self->logMsg(($sid ? "" : "no") . " session id found", $LOG_DEBUG);
# Check for a valid session id
if ($sid and defined(my $rc = $self->check_session($sid)))
{
return $rc;
}
}
else
{
my $service = $self->this_url(1);
$self->logMsg("no session cookie for service: '$service'", $LOG_DEBUG);
}
# No session (or an expired one). Check for a ticket
if (my $ticket = $params{'ticket'})
{
# validate service ticket through CAS, since no valid cookie was found
my($error, $user, $pgtiou) = $self->validate_service_ticket($ticket);
if ($error)
{
return $self->redirect($self->casConfig("ErrorUrl"), $error);
}
# map a new session id to this pgtiou and give the client a cookie
my $sid = $self->create_session($user, $pgtiou, $ticket);
if (!$sid)
{
# if something bad happened, like database unavailability
return $self->redirect($self->casConfig("ErrorUrl"), $ERROR_CODES{"DB"});
}
my $cookie = $self->casConfig("SessionCookieName") . "=$sid;path=/";
if ($self->casConfig("SessionCookieDomain"))
{
$cookie .= ";domain=." . $self->casConfig("SessionCookieDomain");
}
if ($self->casConfig("SessionCookieSecure"))
{
$cookie .= ";secure";
}
# send the cookie to the browser
$self->setHeader(0, 'Set-Cookie', $cookie);
# in case we redirect (considered an "error")
$r->err_headers_out->{"Set-Cookie"} = $cookie;
if ($self->casConfig("ProxyService"))
{
return $self->do_proxy($sid, undef, $user, 1);
}
else
{
$self->setHeader(1, 'CAS_FILTER_USER', $user);
$self->add_basic_auth($user);
# redirect to this same page minus the ticket
return $self->redirect_without_ticket() if ($self->casConfig("RemoveTicket"));
return (Apache2::Const::OK);
}
}
# No valid session, no ticket. Redirect to CAS login
return $self->redirect_login();
}
sub check_session($$$)
{
my($self, $sid) = @_;
# we set up our own session here, so that we don't have to continually
# go through this whole process! we associate a session id with a PGTIOU
# try to get a session record for the session id we received
# session_data - session id, last accessed, netid, pgtiou
lib/Apache2/AuthCAS.pm view on Meta::CPAN
my($self) = @_;
$self->logMsg("redirecting to remove service ticket from service string", $LOG_INFO);
$self->setHeader(0, 'Location', $self->this_url());
return (Apache2::Const::HTTP_MOVED_TEMPORARILY);
}
sub redirect_login($)
{
my($self) = @_;
$self->logMsg("start", $LOG_DEBUG);
my $service = $self->this_url(1);
$self->logMsg("redirecting to CAS for service: '$service'", $LOG_INFO);
$service = uri_escape($service);
$self->setHeader(0, 'Location', "https://"
. $self->casConfig("Host") . ":" . $self->casConfig("Port")
. $self->casConfig("LoginUri") . "?service=$service");
return (Apache2::Const::HTTP_MOVED_TEMPORARILY);
}
sub redirect($;$$)
{
my($self, $url, $errcode) = @_;
if ($url)
{
my $service = $self->this_url(1);
$self->logMsg("redirecting to url: '$url' service: '$service'", $LOG_INFO);
$self->setHeader(0, 'CAS_FILTER_CAS_HOST', $self->casConfig("Host"));
$self->setHeader(0, 'CAS_FILTER_CAS_PORT', $self->casConfig("Port"));
$self->setHeader(0, 'CAS_FILTER_CAS_LOGIN_URI', $self->casConfig("LoginUri"));
$self->setHeader(0, 'CAS_FILTER_SERVICE', $service);
$self->logMsg("redirecting to error page") if ($errcode);
$errcode = "" if (!$errcode);
$service = uri_escape($service);
$self->setHeader(0, 'Location'
, "$url?login_url=https://" . $self->casConfig("Host")
. ":" . $self->casConfig("Port") . $self->casConfig("LoginUri")
. "?service=$service&errcode=$errcode");
return (Apache2::Const::HTTP_MOVED_TEMPORARILY);
}
else
{
$self->logMsg("no redirect URL, displaying message", $LOG_INFO);
$self->{'request'}->content_type('text/html');
$self->{'request'}->print("<html><body>service misconfigured</body></html>");
$self->{'request'}->rflush();
return (Apache2::Const::HTTP_OK);
}
}
# params
# apache request object
# ticket to be validated
# returns a hash with keys on success
# 'user', 'pgtiou'
# NULL on failure
sub validate_service_ticket($$$)
{
my($self, $ticket) = @_;
my $proxy = $self->casConfig("ProxyService") ? "1" : "0";
my $service = $self->this_url(1);
$self->logMsg("Validating service ticket '$ticket' for service '$service'", $LOG_DEBUG);
my $url;
if ($proxy)
{
my $pgtUrl = $self->{'request'}->construct_url();
$url = $self->casConfig("ProxyValidateUri") . "?pgtUrl=$pgtUrl&";
}
else
{
$url = $self->casConfig("ServiceValidateUri") . "?";
}
$service = uri_escape($service);
$url .= "service=$service&ticket=$ticket";
$self->logMsg("request URL: '$url'", $LOG_DEBUG);
# Net::SSLeay::trace options
# 0=no debugging, 1=ciphers, 2=trace, 3=dump data
$Net::SSLeay::trace = ($self->casConfig("LogLevel") >= $LOG_EMERG) ? 3 : 0;
my($page) = Net::SSLeay::get_https(
$self->casConfig("Host"), $self->casConfig("Port"), $url);
$self->logMsg("response page: $page", $LOG_EMERG);
# if we had some type of connection problem
if (!defined($page))
{
$self->logMsg("error validating service");
return ($ERROR_CODES{"CAS_CONNECT"});
}
my $casResponse = eval { XMLin($page); } || {};
my($errorMsg, $user, $pgtiou);
if (my $successBlock = $casResponse->{"cas:authenticationSuccess"})
{
$user = $successBlock->{"cas:user"};
$self->logMsg("valid service ticket, user='$user'", $LOG_DEBUG);
# only try to get PGTIOU if we are doing proxy stuff
if ($proxy)
{
if ($pgtiou = $successBlock->{"cas:proxyGrantingTicket"})
{
$self->logMsg("proxying - pgtiou='$pgtiou'", $LOG_DEBUG);
}
else
{
$self->logMsg("proxying and no pgtiou in response from CAS", $LOG_ERROR);
$errorMsg = $ERROR_CODES{"PGT"};
}
}
( run in 0.854 second using v1.01-cache-2.11-cpan-140bd7fdf52 )