I can't connect using an api - perl

I'm quite new to API's so I don't know if this should be more straight forward.
I write the following perl script
use strict;
use LWP::UserAgent;
require HTTP::Request;
my $request = HTTP::Request->new(GET => 'http://api.elsevier.com/content/ev/results?apiKey=1234&query=stress&database=c&updateNumber=1&pageSize=1');
my $ua = LWP::UserAgent->new;
my $response = $ua->request($request);
then when I get my response and print it in the debugger I get the following
HTTP::Response=HASH(0x9aedff8)
'_content' => '{"service-error":{"status":{"statusCode":"AUTHENTICATION_ERROR","statusText":"Requestor configuration settings insufficient for access to this resource."}}}'
'_headers' => HTTP::Headers=HASH(0x9aedfe8)
'allow' => 'GET'
'client-date' => 'Wed, 29 Mar 2017 08:08:25 GMT'
'client-peer' => '198.185.19.118:80'
'client-response-num' => 1
'content-length' => 156
'content-type' => 'application/json;charset=UTF-8'
'date' => 'Wed, 29 Mar 2017 08:08:24 GMT'
'p3p' => 'CP="IDC DSP LAW ADM DEV TAI PSA PSD IVA IVD CON HIS TEL OUR DEL SAM OTR IND OTC"'
'server' => 'api.elsevier.com 9999'
'vary' => 'Origin'
'x-cnection' => 'close'
'x-els-apikey' => 'e688c9db4db0386581dbe4c4dda46164'
'x-els-reqid' => '0000015b190d89fe-a0d0'
'x-els-status' => 'AUTHENTICATION_ERROR(Requestor configuration settings insufficient for access to this resource.)'
'x-els-transid' => 'cbf787b4-d171-4e35-8237-8cab3c931205'
'x-re-ref' => '1 1490774904423414'
'_msg' => 'Forbidden'
'_protocol' => 'HTTP/1.1'
'_rc' => 403
'_request' => HTTP::Request=HASH(0x9fc3000)
'_content' => ''
'_headers' => HTTP::Headers=HASH(0x9ae73e0)
'user-agent' => 'libwww-perl/5.831'
'_method' => 'GET'
'_uri' => URI::http=SCALAR(0x9e25188)
-> 'http://api.elsevier.com/content/ev/results?apiKey=e688c9db4db0386581dbe4c4dda46164&query=stress&database=c&updateNumber=1&pageSize=1'
'_uri_canonical' => URI::http=SCALAR(0x9e25188)
-> REUSED_ADDRESS
one of the notable lines is
x-els-status' => 'AUTHENTICATION_ERROR(Requestor configuration settings insufficient for access to this resource.)'
I don't know how to get a proper response text. I tried searching their websites for examples, but I can't seem to get it. as well I'm not sure if the key is only for scopus but not engineering village which I'm trying to use.
There website is here. https://dev.elsevier.com/index.html?utm_expid=89327795-0.AtRZzToKQ2u1mZEyQ3n7OQ.0&utm_referrer=https%3A%2F%2Fdev.elsevier.com%2Ftecdoc_ev_retrieval_request.html
any help would be appreciated

To get the text out of your response, you need to call the $response->decoded_content method. That will give you the JSON string that you can see in _content in your debug output. I've indented it to make it easier to read.
{
"service-error" : {
"status" : {
"statusCode" : "AUTHENTICATION_ERROR",
"statusText" : "Requestor configuration settings insufficient for access to this resource."
}
}
}
You can use the JSON module to decode this into a Perl data structure.
use JSON 'from_json';
my $res = $ua->request($req);
my $json = from_json( $res->decoded_content );
The error message you get back clearly states that you are not authenticated properly. I've looked at this guide from the documentation you mentioned. It seems that the apiKey URL param works, if you have the right type of account. You should check with whoever made that account for you, or if that was you and you're not sure, the account manager at that service that is working with you. They'll tell you if you are using the right API key, and if this method of authentication works for you.
Since this API also offers to use a custom header X-ELS-APIKey: [apikey] for the authentication I would suggest using that. Your API key is a secret, and you shouldn't share it with anyone. It's like a password. If you put it into the URL, it might show up in log files. But as a header, it does usually not.
This is how you add a custom header to an HTTP request. Make sure you don't have the apiKey URL param any more if you do this.
my $req = HTTP::Request->new( GET => $url ); # no apiKey=123 here!
$req->header( 'X-ELS-APIKey' => 123 );
Now as a last step, you should check the HTTP response code of the response. A 200 (or most other codes that start with 2) means the request was successful. The 403 that you are getting back means unauthorized, which also hints at that you are not authenticated correctly.
Since it seems that this API returns JSON in both success and failure cases, you might need to decode it for both. If you care to examine the failure response, that makes sense. If not, you can skip that part. To do this, use $res->is_success, which is also used in the synopsis of the LWP::UserAgent documentation.
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request;
use JSON 'from_json';
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new( GET => 'http://api.elsevier.com/content/ev/results?query=stress&database=c&updateNumber=1&pageSize=1' );
$req->header( 'X-ELS-APIKey' => 123 );
if ($req->is_success) {
my $json = from_json( $res->decoded_content );
# ... do stuff with the response
} else {
# something went wrong
}

Related

Debugging HTTP::Tiny

I am using HTTP::Tiny to interact with rest api below an example :
my $req = $ua->get('https://myapi.com/user?xyz', {headers =>
{token => 'xyzzz',
data => '5343'.
}});
i am using Data::Dumper to see the response header and body
print Dumper($req); -#i can see the response
is there an option to see also the HTTP request i am sending using HTTP::Tiny ,without the need of other tool like wireshark etc..
something like what mojo::useragent achieve with MOJO_CLIENT_DEBUG=1 any idea?
I don't see such a feature mentioned in the HTTP::Tiny documentation, nor do I see it facilitated in the source code for the module. However, you can use a module such as Test::MockModule to get a closer look at what is going on. Here is an example:
#!/usr/bin/env perl
use strict;
use warnings;
use HTTP::Tiny;
use Test::MockModule;
use Data::Dumper;
my $t = HTTP::Tiny->new();
my $mock = Test::MockModule->new('HTTP::Tiny');
$mock->redefine('_request' => sub {
warn "In _request: ", Dumper {self => $_[0], method => $_[1], url => $_[2], args => $_[3]};
return $mock->original('_request')->(#_);
});
print "Response: ", Dumper $t->get('http://localhost:3000');
So in this snippet we are mocking _request, but the mock method we install in behalf of _request calls out to the original _request method, so our mocked method becomes a wrapper around the original method, and we can dump the underlying object and args passed into the method. I'm doing the dump prior to the request being made, but I could have done the initial dump, captured the return value of the real request, and then dump the object again afterward if I thought it might contain additional useful information. So long as we return the actual response, the calling code is none the wiser.
Here's an example of the output. The URL I'm hitting is just a default Mojolicious::Lite app.
In _request: $VAR1 = {
'method' => 'GET',
'self' => bless( {
'agent' => 'HTTP-Tiny/0.076',
'no_proxy' => [],
'max_redirect' => 5,
'keep_alive' => 1,
'verify_SSL' => 0,
'timeout' => 60
}, 'HTTP::Tiny' ),
'args' => {},
'url' => 'http://localhost:3000'
};
Response: $VAR1 = {
'headers' => {
'content-length' => '146',
'content-type' => 'text/html;charset=UTF-8',
'server' => 'Mojolicious (Perl)',
'date' => 'Sun, 26 May 2019 03:19:57 GMT'
},
'protocol' => 'HTTP/1.1',
'reason' => 'OK',
'url' => 'http://localhost:3000',
'success' => 1,
'status' => '200',
'content' => '<!DOCTYPE html>
<html>
<head><title>Welcome</title></head>
<body><h1>Welcome to the Mojolicious real-time web framework!</h1>
</body>
</html>
'
};
Before I was able to know that _request was where I wanted to target my mock, I had to look at the source code for HTTP::Tiny. Fortunately the ::Tiny part of the module means there's not a whole lot to look at. It's really a relatively simple module.
You may decide that for your own purposes it makes more sense to introduce your wrapper at some other point in HTTP::Tiny, but wrapping _request seems like a pretty good choice for most cases.

Transmitting POST-event to Google Calendar using Perl and Net::OAuth2

I'm developing a webapp in Perl and try to create events in user's Google Calendar with OAuth2. Authentication and requesting calendar-data is working fine, I'm just totally stuck when it comes to sending a POST-request and attaching JSON- or hash-data to it. Are there methods coming with the module for this? The documentation does not point me anywhere here.
I guess LWP would provide ways, but this seems like a lot of overhead.
Here is how i accomplished getting calendar-events so far (as a simple console-app for now):
use Net::OAuth2::Profile::WebServer;
my $auth = Net::OAuth2::Profile::WebServer->new
( name => 'Google Calendar'
, client_id => $id
, client_secret => $secret
, site => 'https://accounts.google.com'
, scope => 'https://www.googleapis.com/auth/calendar'
, authorize_path => '/o/oauth2/auth'
, access_token_path => '/o/oauth2/token'
, redirect_uri => $redirect
);
print $auth->authorize_response->as_string;
my $code=<STDIN>;
my $access_token = $auth->get_access_token($code);
my $response = $access_token->get('https://www.googleapis.com/calendar/v3/calendars/2j6r4iegh2u8o2409jk8k2g838#group.calendar.google.com/events');
$response->is_success
or die "error: " . $response->status_line;
print $response->decoded_content;
Thanks a lot for your time!
Markus
I guess it's time to answer my own question.
To transmit JSON in a POST-request (creating a calendar-event) I ended up using LWP::UserAgent and HTTP::Request. For setting the content-type I first had to create an HTTP::Request-object and set header and data:
my $req = HTTP::Request->new( 'POST', 'https://www.googleapis.com/calendar/v3/calendars/<calendarID>/events' );
$req->header( 'Content-Type' => 'application/json' );
$req->content( "{ 'summary': 'EventName', 'start': { 'dateTime': '2015-02-11T20:48:00+01:00' }, 'end': { 'dateTime': '2015-02-11T22:30:00+01:00' } }" );
Then I created an LWP::UserAgent-object, appended the OAuth2-token to it and let it fire the request:
my $apiUA = LWP::UserAgent->new();
$apiUA->default_header(Authorization => 'Bearer ' . $access_token->access_token() );
my $apiResponse = $apiUA->request( $req );
It's that simple. Would've been a lot nicer to do this all-in-one with Net::OAuth2, though.

Perl and LWP not authenticating

I'm trying to get an LWP request working to an https server. I have been given a user & pass, advised to use basic authentication. I've tried various chunks of code, and all seem to get an authentication error. My current code is...
use warnings;
use strict;
use Data::Dumper;
use LWP;
my $ua = LWP::UserAgent->new( keep_alive => 1 );
##also tried by $ua->credentials('domain','','user','pass');
##not sure if I need 'realm' or how I get it, as no popup on screen.
my $request = HTTP::Request->new( GET => "https://my.url.com/somepath/" );
$request->authorization_basic('myuser','mypass');
$request->header( 'Cache-Control' => 'no-cache' );
print $response->content;
print Dumper $response;
The server gives a security error, but if I look at a dump of $response, I see the following...
'_rc' => '401',
'_headers' => bless( { .... lots of stuff
'title' => 'Security Exception',
'client-warning' => 'Missing Authenticate header',
'client-ssl-socket-class' => 'IO::Socket::SSL',
...
'expires' => '-1'
}, 'HTTP::Headers' ),
'_msg' => 'Unauthorized',
'_request' => bless( {
'_content' => '',
'_uri' => bless( do{\(my $o = 'https:theurlabove')}, 'URI::https' ),
'_method' => 'GET',
'_uri_canonical' => $VAR1->{'_request'}{'_uri'}
'_headers' => bless( {
'user-agent' => 'libwww-perl/6.04',
'cache-control' => 'no-cache',
'authorization' => 'Basic dzx..........'
}, 'HTTP::Headers' ),
I'm trying to understand whats happening, it looks like in the original request, it has the headers in there, but in the response, its saying I'm 'Missing Authenticate Header'.
Is there something amiss with the code, or something I'm misunderstanding with the request/respinse ?
Thanks.
The "Missing Authenticate header" message is coming from LWP itself. This means that it couldn't find an authenticate header in the target response. This might mean that your proxy settings are misconfigured, if you have anything like that.
I don't know if this is what you are looking for but I came across the same problem trying to authenticate to a webpage and had to solve it with WWW::Mechanize. I had to go to the first page and login then request the page I wanted.
use WWW::Mechanize;
my $loginPage = "http://my.url.com/login.htm"; # Authentication page
my $mech = WWW::Mechanize->new(); # Create new brower object
$mech->get($loginPage); # Go to login page
$mech->form_name('LogonForm'); # Search form named LogonForm
$mech->field("username", myuser); # Fill out username field
$mech->field("password", mypass); # Fill out password field
$mech->click("loginloginbutton"); # submit form
$mech->get("http://my.url.com/somepath/"); # Get webpage
# Some more code here with $mech->content()

Logging into Jenkins via Perl script

I am able to change a build's description with the following program. This will change the build's description to "FOO FOO FOO". Unfortunately, my login doesn't work. Right now, this test Jenkins build server has no security on it. However, on our regular Jenkins server, you need to be logged in to change a build's description.:
#! /usr/bin/env perl
use 5.12.0;
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw(POST);
use Data::Dumper;
use constant {
JENKINS_BASE => "http://build.vegibank.com/",
USER_ID => "buildguy",
PASSWORD => "swordfish",
};
use constant {
LOGIN_URL => JENKINS_BASE . '/j_acegi_security_check',
JOB_URL => JENKINS_BASE . '/job',
SUBMIT_DESCRIPTION => 'submitDescription',
};
my $job_number = 4;
my $job_name = "proginator-2.0";
my $description = "FOO FOO FOO";
my $user_agent = LWP::UserAgent->new or die qq(Can't get User Agent);
#
# My Login Stuff (but it doesn't do anything w/ security off
#
my $response = $user_agent->request (
POST LOGIN_URL,
[
j_username => USER_ID,
j_password => PASSWORD,
],
);
$response = $user_agent->request (
POST "#{[JOB_URL]}/$job_name/$job_number/#{[SUBMIT_DESCRIPTION]}",
[
description => "$description",
],
);
I'm trying to connect to the Jenkins login session, but I don't believe I'm doing it quite right. When I attempt to login, I get a 302 response and the following dump of my response object:
$VAR1 = bless( {
'_protocol' => 'HTTP/1.1',
'_content' => '',
'_rc' => '302',
'_headers' => bless( {
'connection' => 'close',
'client-response-num' => 1,
'set-cookie' => 'JSESSIONID=1D5DF6FAF8714B2ACA4D496FBFE6E983; Path=/jenkins/; HttpOnly',
'location' => 'http://build.vegicorp.com/;jsessionid=1D5DF6FAF8714B2ACA4D496FBFE6E983',
'date' => 'Mon, 13 May 2013 20:02:35 GMT',
'client-peer' => '10.10.21.96:80',
'content-length' => '0',
'client-date' => 'Mon, 13 May 2013 20:02:35 GMT',
'content-type' => 'text/plain; charset=UTF-8',
'server' => 'Apache-Coyote/1.1'
}, 'HTTP::Headers' ),
'_msg' => 'Moved Temporarily',
'_request' => bless( {
'_content' => 'j_username=buildguy&j_password=swordfish',
'_uri' => bless( do{\(my $o = 'http://build.vegicorp.com/j_acegi_security_check')}, 'URI::http' ),
'_headers' => bless( {
'user-agent' => 'libwww-perl/6.03',
'content-type' => 'application/x-www-form-urlencoded',
'content-length' => 42
}, 'HTTP::Headers' ),
'_method' => 'POST',
'_uri_canonical' => $VAR1->{'_request'}{'_uri'}
}, 'HTTP::Request' )
}, 'HTTP::Response' );
I figure I must be hitting a valid page since I'm getting a 302 code, but my fields might not be correct (or I'm going to the wrong page).
Can anyone help?
My authorization is failing because ...what is the technical term? Oh yeah... "doing it all wrong."
After Googling and getting a lot of unhelpful junk, I, on a lark, decided to see if the Jenkins website might have something on this. And, it did right under a page called Authenticating scripted clients. In fact, they even give a Perl LWP example for a scripted client.
Ha ha, I was trying too hard. It seems that Jenkins will use the basic HTTP authentication mechanism, and I don't have to go through conniptions trying to figure out how their login form works. Apparently, Jenkins is simplifying the basic authentication mechanism for you even if your authentication mechanism is far from basic -- like a good program should do.
So, all I had to do was use the basic authentication mechanism.
my $browser = LWP::UserAgent->new or die qq(Cannot get User Agent);
my $request = HTTP::Request->new;
$request->authorization_basic(USER_ID, PASSWORD);
$request->method("GET");
$request->url("$jenkins_url");
my $response = $browser->request($request);
if ( not $response->is_success ) {
die qq(Something went horribly wrong...);
}
I've seen the redirect when the login is successful -- it sets the session cookie and redirects you to the main page.
Your post might be failing because the UA object isn't persisting the session cookie. Per the documentation, 'The default is to have no cookie_jar, i.e. never automatically add "Cookie" headers to the requests.' Try:
my $ua = LWP::UserAgent->new( cookie_jar => HTTP::Cookies->new() );
To store and reuse your session for the description change post.
(Also, credentials are visible in your header dump, may want to edit... Edit: I'm an idiot, they're in your constants too and're likely fake.)

Perl HTTP Request POST fails with TeamCity REST API

I've got a perl script backing up our TeamCity server via the REST API as follows:
use strict;
use LWP::UserAgent;
use HTTP::Request::Common qw{ POST GET }
# ... code ommitted for brevity ... #
my $url = 'http://teamcity:8080/httpAuth/app/rest/server/backup';
my $req = POST( $url . '?includeConfigs=true&includeDatabase=true&includeBuildLogs=true&fileName=' . $filename);
$req->authorization_basic($username, $password);
my $resp = $ua->request($req);
I tried posting the content more in line with the documentation for HTTP:Request, but for some reason it fails, complaining that I haven't specified a file name:
# This fails
my $req= POST( $url, [ 'includeConfigs' => 'true',
'includeDatabase' => 'true',
'includeBuildLogs' => 'true',
'fileName' => $filename,
] );
Yet, when I look at the backend REST log for TeamCity, the full request seems to have made it intact, and is identical to the one that passes above.
Log of successful command:
[2012-12-13 15:02:38,574] DEBUG [www-perl/5.805 ] - rver.server.rest.APIController - REST API request received: POST '/httpAuth/app/rest/server/backup?includeConfigs=true&includeDatabase=true&includeBuildLogs=true&fileName=foo', from client 10.126.31.219, authenticated as jsmith
Log of failed command:
[2012-12-13 14:57:00,649] DEBUG [www-perl/5.805 ] - rver.server.rest.APIController - REST API request received: POST '/httpAuth/app/rest/server/backup?includeConfigs=true&includeDatabase=true&includeBuildLogs=true&fileName=foo', from client 10.126.31.219, authenticated as jsmith
Is there any other hidden difference between the two methods of making a POST request that could be causing the failure?
UPDATE: Here is the result of each request when printed via Data::Dumper
Successful POST:
$VAR1 = bless( {
'_content' => '',
'_uri' => bless( do{\(my $o = 'http://teamcity:8080/httpAuth/app/rest/server/backup?includeConfigs=true&includeDatabase=true&includeBuildLogs=true&fileName=foo')}, 'URI::http' ),
'_headers' => bless( {
'content-type' => 'application/x-www-form-urlencoded',
'content-length' => 0,
'authorization' => 'Basic c3lzQnVpbGRTeXN0ZW1JOnBhaWQuZmFpdGg='
}, 'HTTP::Headers' ),
'_method' => 'POST'
}, 'HTTP::Request' );
Unsuccessful POST:
$VAR1 = bless( {
'_content' => 'includeConfigs=true&includeDatabase=true&includeBuildLogs=true&fileName=foo',
'_uri' => bless( do{\(my $o = 'http://teamcity:8080/httpAuth/app/rest/server/backup')}, 'URI::http' ),
'_headers' => bless( {
'content-type' => 'application/x-www-form-urlencoded',
'content-length' => 75,
'authorization' => 'Basic c3lzQnVpbGRTeXN0ZW1JOnBhaWQuZmFpdGg='
}, 'HTTP::Headers' ),
'_method' => 'POST'
}, 'HTTP::Request' );
I think your server-side script can only handle GET parameters encoded in the URL, not POST data transmitted via standard intput. Note that there are several different methods described by HTTP, these are GET, POST, HEAD, DELETE etc. And then there are two ways of passing data to an application on the server. Most often one of those ways is also called GET parameters and the other one is called POST data because the GET parameters are usually used with the HTTP GET method and POST data is usually used for the HTTP POST method. However, they don't have to. And I think you're mixing up the HTTP POST method with GET parameters in the successful case and with POST data in the unsuccessful case.
GET parameters are passed via the URL by, most often by appending ? to the URL followed by the actual key/value pais. Those are available via certain environment varialbes to the script running on the server. It's up to the script to split the variables at the &, split key/value pairs on = and undo the escaping.
For POST data the environment variable CONTENT_LENGTH tells the script how many bytes to read from its standard input. The actual key/value pairs are transmitted via a different encoding, usually as multipart encoded content. Yes, POST HTTP requests (mostly from HTML <form>s) can also be sent URL-encoded like GET parameters, but there's a length limit imposed by the HTTP standard on the URLs including all parameters. Hence the method of transferring the data via standard input, and not via the URL.
Now it looks like your server-side script can evaluate URL-encoded parameters (aka. GET parameters) parameters but not data posted to it via standard input. Even though you use the POST HTTP method/verb you don't actually transmit the values as POST data via standard input in your successful case. You could simply swap POST(...) for GET(...) in that case and it should still work.
In your unsuccessful case you use the POST HTTP method and the POST data way of transmitting the values.
My verbiage here may be wrong in cases, but the fundamentals should still be OK.
my $url= my $url = 'http://teamcity:8080/httpAuth/app/rest/server/backup';
my $req= POST( $url, { 'includeConfigs' => 'true',
'includeDatabase' => 'true',
'includeBuildLogs' => 'true',
'fileName' => $filename,
} );
Note the '{}' (hash ref, not array ref). Also not mixing the Query String (GET) syntax with the POST syntax goes a long way towards clarifying the issue.
Cheers.