Apache2-AuthCASpbh

 view release on metacpan or  search on metacpan

lib/Apache2/AuthCASpbh/UserAgent.pm  view on Meta::CPAN

package Apache2::AuthCASpbh::UserAgent;

use strict;
use warnings;

our $VERSION = '0.30';

use parent qw(LWP::UserAgent);

use Apache2::AuthCASpbh qw(cfg_value open_session);
use CGI qw ();
use Data::Dumper qw ();
use XML::Simple qw();

sub new {
	my ($class, %conf) = @_;

	exists($conf{apache_r}) or Carp::croak('apache_r argument missing');
	my $r = $conf{apache_r}; delete($conf{apache_r});

	my $cas_cookie_map;
	if (exists($conf{cas_cookie_map})) {
		$cas_cookie_map = $conf{cas_cookie_map}; delete($conf{cas_cookie_map});
	}

	my $self = $class->SUPER::new(%conf);

	$self->{apache_r} = $r;
	$self->{_log} = new Apache2::AuthCASpbh::Log(__PACKAGE__, $r->log);

	if (defined($cas_cookie_map)) {
		$self->{cas_cookie_map} = $cas_cookie_map;
		$self->cookie_jar({}) unless exists($self->{cookie_jar});
	}

	my $dir_cfg = Apache2::Module::get_config('Apache2::AuthCASpbh',
						  $r->server, $r->per_dir_config);
	my $cas_login_url = cfg_value($dir_cfg, 'ServerURL') .
			    cfg_value($dir_cfg, 'LoginPath');

	$self->{debug_level} = cfg_value($dir_cfg, 'DebugLevel');
	$self->{cas_login_url} = qr/^$cas_login_url/;
	$self->{cas_cookie_name} = cfg_value($dir_cfg, 'SessionCookieName');
	$self->{cas_proxy_url} = cfg_value($dir_cfg, 'ServerURL') .
				 cfg_value($dir_cfg, 'ProxyPath');
	$self->{cas_session_db} = cfg_value($dir_cfg, 'SessionDBPath') . '/' .
				  cfg_value($dir_cfg, 'SessionDBName');

	return $self;
}

sub redirect_ok {
	my ($self, $new_request, $response) = @_;
	my $_log = $self->{_log} ;
	my $debug_level = $self->{debug_level};

	if ($response->header('Location') =~ $self->{cas_login_url}) {
		$_log->l($debug_level, 'denying ' . $response->header('Location') .
				       ' redirect, matches ' . $self->{cas_login_url});
		return 0;
	}

	return $self->SUPER::redirect_ok($new_request, $response);
}

sub request {
	my ($self, $request, $arg, $size, $previous) = @_;
	my $_log = $self->{_log};
	my $debug_level = $self->{debug_level};
	my $cas_session = $self->{apache_r}->pnotes('cas_session');
	
	$_log->l('warn', 'no session found for request') and goto NO_SET_COOKIE
		unless defined($cas_session);

	goto NO_SET_COOKIE unless exists($self->{cas_cookie_map});

	my $uri = $request->uri;
	goto NO_SET_COOKIE unless $uri =~ m#http(?:s)?://([^/]+)(/.*)#;
	my ($domain, $path) = ($1, $2);

	foreach my $cme (@{$self->{cas_cookie_map}}) {
		if ($uri =~ /$cme->{URL_re}/) {
			my $url_re = $cme->{URL_re};
			$_log->l($debug_level, "$uri matched $url_re");

			my $session = open_session($self->{cas_session_db}, $cas_session);

			if (ref($session)) {
				foreach my $cookie_key (keys %{$session->{cookies}{$url_re}}) {
					$cookie_key =~ m#([^/]+)(/.*)#;
					my ($cookie_domain, $cookie_path) = ($1, $2);

					if ($domain =~ /\Q$cookie_domain\E$/ && $path =~ /^\Q$cookie_path\E/) {
						$_log->l($debug_level, "adding $cookie_key");

						my @cookie = @{$session->{cookies}{$url_re}{$cookie_key}};
						$self->{cookie_jar}->set_cookie(@cookie);
						last;
					}
				}
				untie(%{$session});
			}
			else {
  				$_log->l('warn', "session tie $cas_session failed - $session");
			}

			last;
		}
	}

	NO_SET_COOKIE:

	my $response = $self->SUPER::request($request, $arg, $size, $previous);

	if ($response->code() == 302 && $response->header('Location') =~ $self->{cas_login_url}) {
		$_log->l($debug_level, "request redirected to CAS login URL $self->{cas_login_url}");

		if (!exists($self->{cas_ua})) {
			$self->{cas_ua} = LWP::UserAgent->new(timeout => 10, keep_alive => 1);
		}

		my $qs = $response->header('Location'); $qs =~ s/^[^\?]+\?//;
		my $q = CGI->new($self->{apache_r}, \$qs);

		my $service = $q->param('service');
		return $_log->l('error', 'no service found in CAS login redirect')
			unless defined($service);

		my $pgt = $self->{apache_r}->pnotes("cas_pgt");
		return $_log->l('error', 'no PGT found for request') unless defined($pgt);

		my $proxy_url = $self->{cas_proxy_url} . "?targetService=" . 
				Apache2::Util::escape_path($service, $self->{apache_r}->pool) .
				"&pgt=$pgt";

		$_log->l($debug_level, "requesting PT via $proxy_url");

		my $response = $self->{cas_ua}->get($proxy_url);

		return $_log->l('error', 'PT request failed - ' . $response->status_line())
			unless $response->is_success();

		my $cas_data = eval { XML::Simple::XMLin($response->content()) };

		return $_log->l('error', "PT request xml parse failed - $@") if ($@);

		if (exists($cas_data->{'cas:proxySuccess'})) {
			my $pt = $cas_data->{'cas:proxySuccess'}{'cas:proxyTicket'};

			my $pt_uri = $service . ($service =~ /\?/ ? '&' : '?') . "ticket=$pt";

			$request->uri($pt_uri);

			$_log->l($debug_level, "resending original request with PT - $pt_uri");
			return $self->request($request, $arg, $size, $previous);
		}
		else {
			if (exists($cas_data->{'cas:proxyFailure'})) {
				$cas_data->{'cas:proxyFailure'}{content} =~ s/^[\s\n]*//;
				$cas_data->{'cas:proxyFailure'}{content} =~ s/[\s\n]*$//;

				return $_log->l('error', 'PT request failed - ' .
						$cas_data->{'cas:proxyFailure'}{content} . ' (' .
						$cas_data->{'cas:proxyFailure'}{code} .')');
			}
			else {
				return $_log->l('error', 'PT request invalid response - ' .
			    			$response->content());
			}
		}
	}

	goto NO_STORE_COOKIE unless exists($self->{cas_cookie_map});



( run in 0.666 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )