Authen-CAS-External
view release on metacpan or search on metacpan
lib/Authen/CAS/External/UserAgent.pm view on Meta::CPAN
package Authen::CAS::External::UserAgent;
use 5.008001;
use strict;
use utf8;
use warnings 'all';
# Module metadata
our $AUTHORITY = 'cpan:DOUGDUDE';
our $VERSION = '0.08';
use Authen::CAS::External::Response 0.05;
use HTML::Form 5.817;
use HTML::TokeParser 3.00;
use HTTP::Status 5.817 qw(HTTP_BAD_REQUEST);
use LWP::UserAgent 5.819;
use Moose::Role 0.89;
use MooseX::Types::Moose qw(Bool Str);
use MooseX::Types::URI qw(Uri);
use Scalar::Util 1.14;
use URI 1.22;
use URI::QueryParam;
# Clean the imports are the end of scope
use namespace::clean 0.04 -except => [qw(meta)];
# Role requires
requires qw(
get_cas_credentials
get_cas_ticket_granting_cookie
);
# Attributes
has previous_response => (
is => 'rw',
isa => 'Authen::CAS::External::Response',
clearer => 'clear_previous_response',
documentation => q{The previous response from a request on the UserAgent},
predicate => 'has_previous_response',
);
has redirect_back => (
is => 'rw',
isa => Bool,
default => 0,
documentation => q{Weither or not for the UserAgent to make a request outside of the CAS site},
);
has user_agent => (
is => 'rw',
isa => 'LWP::UserAgent',
default => sub {
my $ua = LWP::UserAgent->new(cookie_jar => {});
push @{$ua->requests_redirectable}, 'POST';
return $ua;
},
documentation => q{The LWP::UserAgent to use to make requests},
handles => ['get'],
trigger => \&_user_agent_trigger,
);
has cas_url => (
is => 'rw',
isa => Uri,
documentation => q{The URL of the CAS site. This does not include /login},
coerce => 1,
required => 1,
trigger => \&_cas_url_trigger,
);
has _handler_owner_name => (
is => 'ro',
isa => 'Num',
default => sub { Scalar::Util::refaddr(shift); },
init_arg => undef,
);
# Methods
sub service_request_url {
my ($self, %args) = @_;
# Get the CAS URL to use
my $cas_url = exists $args{cas_url} ? $args{cas_url}
: $self->cas_url;
lib/Authen/CAS/External/UserAgent.pm view on Meta::CPAN
if (exists $token->[2]->{class}
&& defined $token->[2]->{class}
&& $token->[2]->{class} =~ m{\berrors?\b}imsx) {
# This token has a class of "error" or "errors" and so should be
# this notification.
$notification = $parser->get_trimmed_text("/$tag");
# End token parsing
last TOKEN;
}
}
# Return the notification
return $notification;
}
sub _extract_service_redirect_link {
my ($response) = @_;
# For the service redirect to be populated into
my $service_redirect;
# Prase the document using HTML::TokeParser
my $parser = HTML::TokeParser->new($response->content_ref);
# Cycle through the tokens on the page
TOKEN: while (my $token = $parser->get_token) {
# Move to the next token if this is not a start tag
next TOKEN
if $token->[0] ne q{S};
# Get the tag of this start tag
my $tag = lc $token->[1];
if ($tag eq q{a}) {
# This is the start of an anchor tag. Anchor tags need to be
# scanned for the service redirect.
if (exists $token->[2]->{href} && $token->[2]->{href} =~ m{ticket=ST-}msx) {
# This is the service redirect link.
# Set the service redirect link from this link
$service_redirect = URI->new($token->[2]->{href});
# End the parsing
last TOKEN;
}
}
}
# Return the service redirect
return $service_redirect;
}
sub _process_login_page {
my ($response, $user_agent, $info) = @_;
my $self = ${$info->{object_instance}};
if ($response->request->method eq 'POST') {
if (!$self->has_previous_response) {
# A POST returning to the login page is a failure
confess 'The login failed with the supplied credentials';
}
# The previous response can determine what occurred
return;
}
# Parse the forms on the page
my @forms = HTML::Form->parse($response->decoded_content, $response->base);
# Find the login form
my $login_form;
FORM: foreach my $form (@forms) {
if (defined $form->find_input('lt')
&& defined $form->find_input('username')
&& defined $form->find_input('password')) {
# Set this as the login form
$login_form = $form;
# Do not continue to search the forms
last FORM;
}
}
if (!defined $login_form) {
confess 'The login form could not be identified on the login page';
}
# The service this form is for
my $service = $login_form->param('service');
# Get the username and password
my ($username, $password) = $self->get_cas_credentials($service);
# Fill in the form
$login_form->param(username => $username);
$login_form->param(password => $password);
# Get the request to make
my $request = $login_form->make_request;
return $request;
}
sub _process_ticket_granting_cookie {
my ($request, $user_agent, $info) = @_;
my $self = ${$info->{object_instance}};
# Clear previous response
$self->clear_previous_response;
# Get the service
my $service = $request->uri->query_param('service');
if (defined $user_agent->cookie_jar) {
# Clear all CAS cookies
$user_agent->cookie_jar->clear($self->cas_url->host);
# Get the CAS credentials
my ($username, $password) = $self->get_cas_credentials($service);
# Get the ticket granting ticket
my $ticket_granting_cookie = $self->get_cas_ticket_granting_cookie(
$username,
$service
);
if (defined $ticket_granting_cookie) {
# Set the cookie for the upcoming request
$user_agent->cookie_jar->set_cookie(
undef,
'CASTGC',
$ticket_granting_cookie,
$self->cas_url->path,
$self->cas_url->host,
$self->cas_url->port,
1,
$self->cas_url->scheme eq 'https',
undef,
0
);
# Add cookies due to HTTP::Config handling
$user_agent->cookie_jar->add_cookie_header($request);
}
}
return;
}
sub _remove_user_agent_handlers {
my ($self, %args) = @_;
# Get the arguments
my ($user_agent, $cas_url) = @args{qw(user_agent cas_url)};
# Default arguments
$cas_url ||= $self->cas_url;
$user_agent ||= $self->user_agent;
# Remove the handlers in the user agent
$user_agent->remove_handler(undef,
m_host => $cas_url->host,
owner => $self->_handler_owner_name,
);
return;
}
sub _user_agent_trigger {
my ($self, $user_agent, $previous_user_agent) = @_;
if (defined $previous_user_agent) {
# Remove the handlers from the previous user agent
$self->_remove_user_agent_handlers(
user_agent => $previous_user_agent,
);
}
# Now add the handlers to the new user agent
lib/Authen/CAS/External/UserAgent.pm view on Meta::CPAN
sub BUILD {
my ($self) = @_;
# Hook the respose handler
$self->_add_user_agent_handlers();
return;
}
sub DEMOLISH {
my ($self) = @_;
# Remove the handlers from the user agent
$self->_remove_user_agent_handlers;
return;
}
sub FOREIGNBUILDARGS {
my ($class, @args) = @_;
# According to LWP::UserAgent, takes straight hash
if (@args % 2 == 1) {
confess 'Invalid arguments passed';
}
# The defaults for WWW::Mechanize
my %args = (
autocheck => 0,
noproxy => 1,
stack_depth => 1,
@args,
);
# Return the args to the super class
return %args;
}
1;
__END__
=head1 NAME
Authen::CAS::External::UserAgent - UserAgent role for CAS session managers.
=head1 VERSION
This documentation refers to version 0.08.
=head1 SYNOPSIS
package MyCAS::Session;
use Moose;
# Use this role
with 'Authen::CAS::External::UserAgent';
sub get_cas_credentials {
my ($self, $service) = @_;
# Do something
return $username, $password;
}
sub get_cas_ticket_granting_cookie {
my ($self, $username, $service) = @_;
# Do something
return $TGC;
}
1;
=head1 DESCRIPTION
Provides a way to authenticate with a CAS server just as a browser
would. This is useful with web scrapers needing to login to a CAS
site.
=head1 ROLE REQUIRES
This is a L<Moose::Role|Moose::Role> and for this role to be used, the user
MUST provide the following two methods:
=head2 get_cas_credentials
This is called as a method with the first argument being a string that is the
URL of the service that is about to be logged in to. If no service is being
logged in to, then it will be undefined. This function is expected to return
a username string and a password string, both of which are optional, but MUST
be returned in that order.
=head2 get_cas_ticket_granting_cookie
This is called as a method with the first argument being a string that is the
username being used and the second argument being a string that is the URL of
the service that is about to be logged into. This function is expected to
return a string that is the ticket granting cookie for the CAS service, or
nothing.
=head1 ATTRIBUTES
=head2 cas_url
This is a L<URI|URI> object of the base URL of the CAS site. This is typically
the path before C</login>. A string may be supplied and will automatically
be converted to a L<URI|URI> object.
=head2 previous_response
This holds the response object
L<Authen::CAS::External::Response|Authen::CAS::External::Response> from the
last executed CAS navigation.
=head2 redirect_back
This is a Boolean that determines if the L</user_agent> will navigate
outside of the L</cas_url>. The default is C<0>.
=head2 user_agent
This is a L<LWP::UserAgent|LWP::UserAgent> that is used to navigate the CAS
site. The default is L<LWP::UserAgent|LWP::UserAgent> with an in-memory cookie
jar and allows the C<POST> method to be redirectable.
=head1 METHODS
=head2 clear_previous_response
This will clear the L</previous_response> attribute.
=head2 get
This is an alias to the C<get> method of the L</user_agent>.
=head2 has_previous_response
This will return if there is a value present in the L</previous_response>
attribute.
=head2 service_request_url
B<service_request_url(%args)>
This method will return a URI object that is the URL to request for the CAS
( run in 1.520 second using v1.01-cache-2.11-cpan-39bf76dae61 )