GRNOC-WebService-Client

 view release on metacpan or  search on metacpan

lib/GRNOC/WebService/Client.pm  view on Meta::CPAN

    }

}

sub _redirect_timing {
    my $self = shift;

    return sub {
        my ($response, $ua, $h) = @_;

        if ($self->{'timing'} && $response->header("location")){
            $self->_do_timing("Redirect to " . $response->header("location"));
        }

	return;
    }
}

sub _do_timing {
    my $self    = shift;
    my $message = shift;

    my $timestamp = [gettimeofday];
    my $elapsed   = tv_interval($self->{'start_time'}, $timestamp);
    my $diff;

    if ($self->{'last_timestamp'}){
        $diff = tv_interval($self->{'last_timestamp'}, $timestamp);
    }

    $self->{'last_timestamp'} = $timestamp;

    my $str = "$message ... elapsed time = $elapsed seconds";

    if ($diff){
        $str .= " (+ $diff)";
    }

    print $str . "\n";
}


#--- protected method used to get content. Can traverse cosign, basic auth, and unprotected
#--- resources transparently.
sub _fetch_url {

    my $self        = shift;
    my $request     = shift;      #--- reference to HTTP::Request object
    my $username    = $self->{'uid'};
    my $passwd      = $self->{'passwd'};
    my $realm       = $self->{'realm'};
    my $cookieJar   = $self->{'cookieJar'};
    my $ua          = $self->{'ua'};

    #--- if we did not pass realm explicitly
    #--- use realm from the config file
    if( !defined( $self->{'realm'} )) {
        $self->{'realm'} = $self->{'default_realm'};
    }

    #--- set credentials for basic auth if given
    #--- this does not use LWP::UserAgent->credentials because that appears to do two requests
    #--- because it won't send credentials until it gets challenged, so we set the creds
    #--- directly on the request
    if (defined $self->{'uid'} && defined $self->{'passwd'} && defined $self->{'realm'}){
      # --- Is this a Shibboleth ECP realm?
      if ($self->{'realm'} =~ m|^https://|){
        # Then set PAOS accept/header to tell SP we want to ECP
        $request->header('Accept' => "*/*; @{[CONTENT_PAOS]}");
        $request->header('PAOS' => PAOS_HEADER);
      }
      else {
        # Otherwise do basic auth
        $request->authorization_basic($self->{'uid'}, $self->{'passwd'});
      }

    }

    if ($self->{"timing"}) {
        $self->{'start_time'}     = [gettimeofday];
        $self->{'last_timestamp'} = undef;
        print "Request is initiated...\n";
    }

    my $timed_out = 0; #timeout check for $request
    local $SIG{ALRM} = sub {
        #request has timed out
        $timed_out = 1;
    };
    if(defined $self->{'timeout'}){
        #if timeout is defined
        alarm $self->{'timeout'};
    }
    else{
        # don't alarm
        alarm 0;
    }
    #--- get the initial URL
    my $result = $ua->request($request);
    alarm 0;
    if($timed_out){
        #Request timed out--->alarm
        $self->_set_error("Request timeout.." . $request->uri());
        return undef;
    }
    if ($result->is_success && !defined($result->header('x-died'))){

        my $content = $result->content;

        #--- We're at cosign
        if ($content =~ /<form action=\".*cosign-bin\/cosign\.cgi/mi){
          return $self->_do_cosign_login($request, $content, $result);

        }
        #--- We're at Shib ECP
        elsif (defined($result->header('content-type')) && $result->header('content-type') eq CONTENT_PAOS) {
          return $self->_do_ecp_login($request, $content);
        }
        #--- We're not at cosign or doing ECP login, this must be the final result.
        else {
            if ($self->{"timing"}) {
                $self->_do_timing("Success");
            }

lib/GRNOC/WebService/Client.pm  view on Meta::CPAN

        }
    }
    #--- Failure
    else {
        if ($self->{"timing"}) {
            $self->_do_timing("Failed");
        }

	my $error = $result->header('x-died') || $result->message;

        $self->_set_error("HTTP Error: $error : " . $request->uri());
        return undef;
    }

}

sub _do_cosign_login {
    my $self      = shift;
    my $request   = shift;
    my $content   = shift;
    my $result    = shift;
    my $username  = $self->{'uid'};
    my $passwd    = $self->{'passwd'};
    my $ua        = $self->{'ua'};
    my $timed_out = 0;

    if ($self->{timing}) {
        $self->_do_timing("Request is redirected to Cosign");
    }

    my $form = HTML::Form->parse($content, $result->base());

    if (!defined $form) {
        $self->_set_error("Redirected to something I can't parse:\n" . $content . "\n");
        return undef;
    }

    #--- fill out login parameters
    $form->value("login",$username);
    $form->value("password",$passwd);
    my $request2 = $form->click;
    local $SIG{ALRM} = sub {
        #request2 timed out
        $timed_out = 1;
    };
    if(defined $self->{'timeout'}){
        alarm $self->{'timeout'};
    }
    else{
        alarm 0;
    }
    #--- submit form
    my $result2 = $ua->request($request2);
    alarm 0;
    if($timed_out){
        #request2 timed out----> alarm
        $self->_set_error("Request timeout while authing to cosign.." . $request2->uri());
        return undef;
    }
    if ($self->{"timing"}) {
        $self->_do_timing("Sent credentials to Cosign");
    }

    #--- Got another 200 back
    if ($result2->is_success && !defined($result2->header('x-died'))){

        my $content2 = $result2->content;

        #--- Are we back at Cosign? If so, we're unauthorized.
        if ($content2 =~ /<form action=\".*cosign-bin\/cosign\.cgi\"/mi){
            $self->_set_error( "Error: Authorization failed for: " . $request->uri());
            return undef;
        }

        #--- Otherwise we're good, return content
        $self->{'content_type'} = $result2->header('content-type');
        $self->{'headers'}      = $self->_parse_headers($result2);

        return $content2;
    }
    else {
        #--- Something went wrong in getting the final url after cosign auth succeeded
        my $error = $result2->header('x-died') || $result2->message;
        $self->_set_error("HTTP Error after logging into Cosign: $error");
        return undef;
    }
}

#-- helper for handling Shib ECP login
sub _do_ecp_login {
    my $self    = shift;
    my $request = shift;
    my $content = shift;

    my $ua        = $self->{'ua'};
    my $timed_out = 0;

    if ($self->{timing}) {
        $self->_do_timing("Request is wanting ECP login");
    }
    my $doc;
    # Convert ECP response to what we send to IdP
    eval{$doc = $self->{'xmlparser'}->parse_string($content);};
    if ($@) {
        $self->_set_error("Unable to parse ECP XML: " . $@);
        return undef;
    }

    my @tmp = $self->{'xpath'}->findnodes('//S:Envelope/S:Header/ecp:RelayState', $doc);
    if (!(scalar @tmp == 1)) {
        $self->_set_error("Unable to find RelayState");
        return undef;
    }
    my $relaystate = $tmp[0];

    my $responseconsumer = $self->{'xpath'}->findvalue('//S:Envelope/S:Header/paos:Request/@responseConsumerURL', $doc);

    @tmp = $self->{'xpath'}->findnodes('//S:Envelope', $doc);
    if (!(scalar @tmp == 1)) {
        $self->_set_error("Unable to find Envelope");
        return undef;

lib/GRNOC/WebService/Client.pm  view on Meta::CPAN


sub clear_urls {
    my $self = shift;

    $self->{'urls'} = undef;
}

=head2 set_service_identifier

    interface to change what service identifier we are using.
Note: This wipes out any existing URLs that may have been loaded from a service identifier

=cut

sub set_service_identifier {

    my $self = shift;
    my $sid  = shift;

    #--- need to clean out old urls first or they could be queried instead of the one we need.
    $self->clear_urls();

    $self->{'service_name'} = $sid;

    # we might have been initialized without a service identifier in which case we wouldn't have
    # any service_urls loaded yet so try to load them if that's the case
    if ($self->{'service_cache_file'})
    {
        if (! $self->_load_config()) {
            $self->_set_error("No service urls found and unable to load config.");
            return undef;
        }

        # figure out what URLs to know about based on the passed in service identifier or bail
        if (! $self->_setup_urls($self->{'service_name'}))
        {
            $self->_set_error("Unable to find a usable URL for URN = " . $self->{'service_name'} . " in service cache file \"" . $self->{'service_cache_file'} . "\"\n");
            return undef;
        }
    }
    elsif (defined($self->{'name_services'}))
    {
        #get the NameService locations
        $self->_ns_service_lookup();

        if (! $self->_setup_urls($self->{'service_name'}))
        {
            #-- no url provided and none resolved from service name
            $self->_set_error("Unable to find a usable URL for URN = " . $self->{'service_name'} . " in name services: " . Dumper($self->{'name_services'}) . "\n");
            return undef;
        }
    }
    else
    {
        $self->_set_error("Unable to find a usable URL: Neither name_services or service_cache_file were specified\n");
        return undef;
    }
    return 1;
}

=head2 set_credentials

    interface to change the username, password, and/or realm of the client

=cut

sub set_credentials {
    my $self = shift;
    my %args = @_;

    $self->{'uid'}    = $args{'uid'} if ($args{'uid'});
    $self->{'realm'}  = $args{'realm'} if ($args{'realm'});
    $self->{'passwd'} = $args{'passwd'} if ($args{'passwd'});

    return 1;
}

=head2 set_cookies

    interface to change cookies of an existing client, useful in stateful mod_perl. Object given must be a HTTP::Cookies object

=cut

sub set_cookies {
    my $self        = shift;
    my $cookies_obj = shift;

    if (!defined $cookies_obj) {
        return undef;
    }

    $self->{'ua'}->cookie_jar($cookies_obj);

    return 1;
}


=head1 AUTHOR

    GRNOC Systems Engineering, C<< <syseng at grnoc.iu.edu> >>

=head1 BUGS

    Please report any bugs or feature requests to C<< <syseng at grnoc.iu.edu> >>





=head1 SUPPORT

    You can find documentation for this module with the perldoc command.

    perldoc GRNOC::WebService::Client


=head1 ACKNOWLEDGEMENTS

=cut

1;



( run in 0.634 second using v1.01-cache-2.11-cpan-d7f47b0818f )