POST API in Perl using LWP::UserAgent with authentication - perl

I am trying to use POST method in perl to send information to an API.
I would like to call the below api which requires following inputs:
URI: https://www.cryptopia.co.nz/api/SubmitTrade
Input Parameters are:-
Market: The market symbol of the trade e.g. 'DOT/BTC' (not required if 'TradePairId' supplied)
TradePairId: The Cryptopia tradepair identifier of trade e.g. '100' (not required if 'Market' supplied)
Type: the type of trade e.g. 'Buy' or 'Sell'
Rate: the rate or price to pay for the coins e.g. 0.00000034
Amount: the amount of coins to buy e.g. 123.00000000
Please can you tell me how I can call this api from perl ?
Request Structure:
REQUEST_SIGNATURE: API_KEY + "POST" + URI + NONCE + HASHED_POST_PARAMS
API_KEY: Your Cryptopia api key
URI: the request uri. e.g. https://www.cryptopia.co.nz/Api/SubmitTrade
HASHED_POST_PARAMS: Base64 encoded MD5 hash of the post parameters
NONCE: unique indicator for each request.
So question is how do I join this api https://www.cryptopia.co.nz/api/SubmitTrade and pass the arguments to it with authentication and check if returned result is success?
Result Example:
{
"Success":true,
"Error":null,
"Data":
{
"OrderId": 23467,
"FilledOrders": [44310,44311]
}
}

I don't have a Cryptopia account to test this, but this might at least get you closer to a working prototype.
Setup
Load the required modules and create a LWP::UserAgent object.
use strict;
use warnings;
use LWP::UserAgent;
use JSON;
use Digest::MD5 qw(md5);
use Digest::SHA qw(hmac_sha256);
use MIME::Base64;
use URI::Encode qw(uri_encode);
my $api_key = 'PUBLIC KEY';
my $api_private_key = 'PRIVATE KEY';
my $ua = LWP::UserAgent->new;
Generating the request
The tricky part is generating the authorization header. The API keys for your account will be base64-encoded - hence the call to decode_base64() when using it to sign the request.
my $url = "https://www.cryptopia.co.nz/api/GetTradeHistory";
my %req = (
Market => 'DOT/BTC',
);
my $nonce = int(rand(1000000));
my $post_data = encode_json(\%req);
my $post_data_enc = encode_base64(md5($post_data), "");
my $encoded_url = lc(uri_encode($url, encode_reserved => 1));
my $req_signature = sprintf("%sPOST%s%s%s", $api_key, $encoded_url, $nonce, $post_data_enc);
# Sign request signature with private key.
my $req_signature_hmac = encode_base64(hmac_sha256($req_signature, decode_base64($api_private_key)));
# Generate value for 'Authorization' header field.
my $auth_header_value = sprintf("amx %s:%s:%s", $api_key, $req_signature_hmac, $nonce);
Notes ...
An empty string is passed as the second argument to encode_base64() - to prevent it from appending a new-line to the output.
uri_encode() from the URI::Encode module is used the encode the URL.
You might want to use an alternative source or random generator for the nonce.
Sending the request
You could create a HTTP::Request object explicitly and pass that to LWP::UserAgent's request() method, but there is a post() convenience method which does all this for you.
The example here uses the Content parameter to set the content of the post body, and sets the Authorization and Content-Type headers at the same time.
my $response = $ua->post($url,
Content => $post_data,
'Content-Type' => 'application/json',
Authorization => $auth_header_value
);
die "Request failed: ", $response->content unless $response->is_success();
print $response->content, $/;
Checking the response
It may be enough to check the LWP Response object as above - by invoking the $response->is_success() method. Nonetheless, if you want to explicitly check for success, just decode the JSON response - using $resp = decode_json($response->decoded_content). Then examine the relevant keys in the resulting hash.

Related

Postman Oauth 1.0 signature does not match what I get in Perl

I'm trying to authenticate to the Here.com API via Oauth 1.0. I have a working Postman request that successfully gets an authentication token. I tried implementing the signature-generating procedure in Perl, but the signature I get is different from the one Postman gets. I also checked this sandbox using the same parameters, and it seem to generate the same signature I get, however this signature fails and the one that Postman generates succeeds, and I honestly cannot figure out why. Let me share some examples:
This is the code I'm playing with in Perl, with test parameters:
use URI::Encode;
use Digest::SHA qw(hmac_sha256 hmac_sha1 hmac_sha256_base64);
use MIME::Base64;
my $oauth_values = {
consumer_key => "my-test-key",
consumer_secret => "my-test-secret",
request_url => "https://account.api.here.com/oauth2/token",
request_method => "POST",
signature_method => "HMAC-SHA256",
timestamp => 1234567890,
nonce => "test-nonce",
protocol_version => "1.0",
};
my $encoder = URI::Encode->new( { encode_reserved => 1 } );
my $signature_base_string = "oauth_consumer_key=$oauth_values->{consumer_key}" .
"&oauth_nonce=$oauth_values->{nonce}" .
"&oauth_signature_method=$oauth_values->{signature_method}" .
"&oauth_timestamp=$oauth_values->{timestamp}" .
"&oauth_version=$oauth_values->{protocol_version}";
my $base_string = "POST&" . $encoder->encode($oauth_values->{request_url}) . "&" . $encoder->encode($signature_base_string);
print "Signature base string: $signature_base_string\n";
print "Base string: $base_string\n";
my $signature = encode_base64(hmac_sha256($base_string, $oauth_values->{consumer_secret} . "&"));
print "Signature: $signature\n";
Note that the params on the base string are in alphabetical order, as they should.
The output I get from that code is the following:
Signature base string: oauth_consumer_key=my-test-key&oauth_nonce=test-nonce&oauth_signature_method=HMAC-SHA256&oauth_timestamp=1234567890&oauth_version=1.0
Base string: POST&https%3A%2F%2Faccount.api.here.com%2Foauth2%2Ftoken&oauth_consumer_key%3Dmy-test-key%26oauth_nonce%3Dtest-nonce%26oauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3D1234567890%26oauth_version%3D1.0
Signature: KOXTa8e/Vw083CAwZctnZiJVIvAjH1aw4/5RWXeIhX4=
If I put these same parameters on the sandbox, I get the same signature:
sandbox signature screenshot
However, when I put the exact same parameters in Postman:
Postman parameter settings
then the signature that Postman generates is different:
OxhDiuqUEBAd45vNn4zIy/0etSVOn2fvquw+kQMxwsg=
This is what I get when I generate the code snippet in Python:
import requests
url = "https://account.api.here.com/oauth2/token"
payload = ""
headers = {
'Authorization': 'OAuth oauth_consumer_key="my-test-key",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1234567890",oauth_nonce="test-nonce",oauth_version="1.0",oauth_signature="OxhDiuqUEBAd45vNn4zIy/0etSVOn2fvquw+kQMxwsg="'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
I've even read the RFC with the steps, and I cannot spot an error in my method.
So how come that Postman signature is correct and the one I generate is not? What am I missing or doing wrong?
I was able to figure it out. It turns out that encode_base64 is adding a new line at the end, which was breaking the signature I sent in the request. Adding chomp right after it fixed it.
Also, the difference I was getting between Postman and Perl signatures was due to the way Postman calculates the base string when the URL is parametrised, apparently, as switching to a "hardcoded" URL made the signatures coincide. Surprisingly, in both cases Postman is able to get an auth token, which leads me to think that the signature I get when I peek into the "Code" option in Postman is not necessarily the same one that is being sent in the request.
In a nutshell, doing this works:
my $digest = encode_base64(hmac_sha256($base_string, $oauth_values->{consumer_secret} . "&"));
chomp $digest;
my $signature_encoded = $encoder->encode($digest);

How may I bypass LWP's URL encoding for a GET request?

I'm talking to what seems to be a broken HTTP daemon and I need to make a GET request that includes a pipe | character in the URL.
LWP::UserAgent escapes the pipe character before the request is sent.
For example, a URL passed in as:
https://hostname/url/doSomethingScript?ss=1234&activities=Lec1|01
is passed to the HTTP daemon as
https://hostname/url/doSomethingScript?ss=1234&activities=Lec1%7C01
This is correct, but doesn't work with this broken server.
How can I override or bypass the encoding that LWP and its friends are doing?
Note
I've seen and tried other answers here on StackOverflow addressing similar problems. The difference here seems to be that those answers are dealing with POST requests where the formfield parts of the URL can be passed as an array of key/value pairs or as a 'Content' => $content parameter. Those approaches aren't working for me with an LWP request.
I've also tried constructing an HTTP::Request object and passing that to LWP, and passing the full URL direct to LWP->get(). No dice with either approach.
In response to Borodin's request, this is a sanitised version of the code I'm using
#!/usr/local/bin/perl -w
use HTTP::Cookies;
use LWP;
my $debug = 1;
# make a 'browser' object
my $browser = LWP::UserAgent->new();
# cookie handling...
$browser->cookie_jar(HTTP::Cookies->new(
'file' => '.cookie_jar.txt',
'autosave' => 1,
'ignore_discard' => 1,
));
# proxy, so we can watch...
if ($debug == 1) {
$browser->proxy(['http', 'ftp', 'https'], 'http://localhost:8080/');
}
# user agent string (pretend to be Firefox)
$agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.7.12) Gecko/20050919 Firefox/1.0.7';
# set the user agent
$browser->agent($agent);
# do some things here to log in to the web site, accept session cookies, etc.
# These are basic POSTs of filled forms. Works fine.
# [...]
my $baseURL = 'https://hostname/url/doSomethingScript?ss=1234&activities=VALUEA|VALUEB';
#values = ['Lec1', '01', 'Lec1', '02'];
while (1) {
if (scalar(#values) < 2) { last; }
my $vala = shift(#values);
my $valb = shift(#values);
my $url = $basEURL;
$url =~ s/VALUEA/$vala/g;
$url =~ s/VALUEB/$valb/g;
# simplified. Would usually check request for '200' response, etc...
$content = $browser->get($url)->content();
# do something here with the content
# [...]
# fails because the '|' character in the url is escaped after it's handed
# to LWP
}
# end
As #bchgys mentions in his comment, this is (almost) answered in the linked thread. Here are two solutions:
The first and arguably cleanest one is to locally override the escape map in URI::Escape to not modify the pipe character:
use URI;
use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
my $res;
{
# Violate RFC 2396 by forcing broken query string
# local makes the override take effect only in the current code block
local $URI::Escape::escapes{'|'} = '|';
$res = $ua->get('http://server/script?q=a|b');
}
print $res->request->as_string, "\n";
Alternatively, you can simply undo the escaping by modifying the URI directly in the request after the request has been created:
use HTTP::Request;
use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
my $req = HTTP::Request->new(GET => 'http://server/script?q=a|b');
# Violate RFC 2396 by forcing broken query string
${$req->uri} =~ s/%7C/|/;
my $res = $ua->request($req);
print $res->request->as_string, "\n";
The first solution is almost certainly preferable because it at least relies on the %URI::Escape::escapes package variable which is exported and documented, so that's probably as close as you're gonna get to doing this with a supported API.
Note that in either case you are in violation of RFC 2396 but as mentioned you may have no choice when talking to a broken server that you have no control over.

How can I make a HTTP PUT request in perl that contains application/x-www-form-urlencoded data?

How can I make a HTTP PUT request in Perl that contains application/x-www-form-urlencoded data?
This is an equivalent POST request that works:
my $ua = new LWP::UserAgent;
my $response = $ua->post(
$url,
{
"parameter1" => $value1,
"parameter2" => $value2
}
);
How would this be done as a PUT request?
There is no put method in LWP and the PUT function in HTTP::Request::Common does not take form data.
For a discussion if a PUT request with form data is allowed, see Can HTTP PUT request have application/x-www-form-urlencoded as the Content-Type?
This is an example of a PUT request, but it does not contain code to enclose form data: How to make a HTTP PUT request using LWP?
Just make POST-request and change its method to PUT:
use HTTP::Request::Common;
my $req = POST('http://example.com/', Content => [param => 'value']);
$req->method('PUT');
say($req->as_string);

Can I pass GET string in UserAgent post method

I call in this mode:
my $ua = new LWP::UserAgent;
my $response= $ua->post('www.example.com', {param1=>'val1',param2=>'val2'...} );
Can I call the above in the same way passing the values in GET form?:
my $response= $ua->post('www.example.com?param=val1&param2=val2' );
It is because I'm using Firebug and when I go to Net tab under the "POST" tab it shows individual parameters as well as a GET string for POST submitted requests.
So I was wondering if I use GET string in this function call.
Parametersapplication/x-www-form-urlencoded
Itemid 4 option com_search
searchword dsd task search Source
Content-Type:
application/x-www-form-urlencoded
Content-Length: 53
searchword=dsd&task=search&option=com_search&Itemid=4
In short you can pass GET strings yes, but if your end code does not accept GET METHOD it will fail.
Also you might still need to specify some parameters since the post method asks for post(url,array_with_parameters).
sub post {
require HTTP::Request::Common;
my($self, #parameters) = #_;
my #suff = $self->_process_colonic_headers(\#parameters, (ref($parameters[1]) ? 2 : 1));
return $self->request( HTTP::Request::Common::POST( #parameters ), #suff );
}
Using along with HTTP::Request you can specify it at the content in the way you prefer:
# Create a user agent object
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->agent("MyApp/0.1 ");
# Create a request
my $req = HTTP::Request->new(POST => 'http://www.example.com');
$req->content_type('application/x-www-form-urlencoded');
$req->content('searchword=dsd&task=search&option=com_search&Itemid=4');
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
# Check the outcome of the response
if ($res->is_success) {
print $res->content;
} else {
print $res->status_line, "\n";
}
Read more...

Adding authHeader to Perl SOAP::Lite request

I am having some trouble creating a request to this WSDL that works; it requires authHeaders and I am not having much luck adding them. This is what I am trying:
# make proxy for the service
my $soap = SOAP::Lite->service($wsdl);
# add fault hanlder
$soap->on_fault(
sub { # SOAP fault handler
my $soap = shift;
my $res = shift;
# Map faults to exceptions
if(ref($res) eq '') {
die($res);
}
else {
die($res->faultstring);
}
return new SOAP::SOM;
}
);
# authentication request headers
my #headers = (
SOAP::Header->name('user')->value('myemail#whatever.com')->uri($apins),
SOAP::Header->name('password')->value('mypassword')->uri($apins),
SOAP::Header->name('appName')->value('TestApp')->uri($apins),
SOAP::Header->name('appVersion')->value('0.02')->uri($apins)
);
# request method
print $soap->getCompanyInfo('NB', #headers);
The response I get when doing this is:
String value expected instead of SOAP::Header reference
The method I am requesting has two string parameters, both optional. And suggestions?
I was able to get help form the SOAP::Lite mailing list. If I want to pass my own headers, I have to use the call method instead of the actually method name.
# create header for requests
my $authHeader = SOAP::Header->name("xsd:authHeader" =>
\SOAP::Header->value(
SOAP::Header->name('xsd:user')->value($s7user)->type(''),
SOAP::Header->name('xsd:password')->value($s7pass)->type(''),
SOAP::Header->name('xsd:appName')->value('TestApp')->type(''),
SOAP::Header->name('xsd:appVersion')->value('0.03')->type('')
));
# create data to pass as method paramaters
my $params = SOAP::Data->name('ns:email')->value($s7user)->type('');
# request method
$soap->call('checkLogin', $params, $authHeader);
In order to use the call method, you will need to define a proxy (endpoint) on your soap object. Hope this is helpful for someone else down the road.