Indy10 Google Securetoken always 403 - delphi

I am struggling to get a response from Google Securetoken using Delphi 10.2.2 and Indy 10.
I've gotten my RefreshToken before from https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=XXX. This worked fine.
Now I try to refresh the token with https://securetoken.googleapis.com/v1/token, which doesn't work. I always get a 403 Forbidden error.
procedure TForm1.Button2Click(Sender: TObject);
var
l_Response: string;
l_PostData: TIdMultiPartFormDataStream;
l_IRESAccessToken: TIRESAccessToken;
begin
IdHTTP2.Request.ContentType := 'application/json';
IdHTTP2.Request.CharSet := 'utf-8';
l_PostData := TIdMultiPartFormDataStream.Create;
try
l_PostData.AddFormField('grant_type', 'refresh_token');
l_PostData.AddFormField('refresh_token', m_IRESAuth.RefreshToken);
l_PostData.AddFormField('key', 'XXX');
try
l_Response := IdHTTP2.Post('https://securetoken.googleapis.com/v1/token', l_PostData);
l_IRESAccessToken := TJson.JsonToObject<TIRESAccessToken>(l_Response);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
l_PostData.Free;
end;
end;
I tried an IOHandler that specifies TLS 1.2, and tried to send the post with a JSON object. Also, I set the hoKeepOrigProtocol flag in the TIdHTTP.HTTPOptions. Nothing worked so far, I always get 403 Forbidden.
I tried it with another program, no problems there.
Am I missing something?

Per Google's documentation:
Token Service REST API Reference:
HTTP request
POST https://securetoken.googleapis.com/v1/token
Request body
The request body contains data with the following structure:
URL-encoded representation
grant_type=string&code=string&refresh_token=string
The TStrings overload of TIdHTTP.Post() sends data in that format. That is the overload you need to use, not the TIdMultipartFormDataStream overload.
Also, as you can see above, this URL does not accept your key as input. It only accepts grant_type, code (which is ignored when grant_type is not 'authorization_code') and refresh_token.
Try this instead:
procedure TForm1.Button2Click(Sender: TObject);
var
l_Response: string;
l_PostData: TStringList;
l_IRESAccessToken: TIRESAccessToken;
begin
IdHTTP2.Request.ContentType := 'application/x-www-webform-urlencoded';
l_PostData := TStringList.Create;
try
l_PostData.Add('grant_type=refresh_token');
l_PostData.Add('refresh_token=' + m_IRESAuth.RefreshToken);
try
l_Response := IdHTTP2.Post('https://securetoken.googleapis.com/v1/token', l_PostData);
l_IRESAccessToken := TJson.JsonToObject<TIRESAccessToken>(l_Response);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
l_PostData.Free;
end;
end;
That being said, after reading the above documentation, I don't believe you can send an idToken from https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword to https://securetoken.googleapis.com/v1/token as a refresh_token. I think you need to send it as an authorization_code instead, which then gives you a refresh_token. So try adding that extra step, if you have not already done so.

Sorry for the late response and thank you for the answer.
After consultation with the Main-API-Developer, he told me that they are using Firebase in the background. So the API request für Google is different.
https://cloud.google.com/identity-platform/docs/use-rest-api#section-sign-in-email-password
https://cloud.google.com/identity-platform/docs/use-rest-api#section-refresh-token
With this missing information the request works fine
procedure TForm1.Button2Click(Sender: TObject);
var
l_Response: string;
l_IRESAccessToken: TIRESAccessToken;
l_Json: string;
l_JsonToSend: TStringStream;
begin
l_Json := '{"grant_type": "refresh_token","refresh_token": "'+ m_IRESAuth.RefreshToken+ '"}';
l_JsonToSend := TStringStream.Create(l_Json, TEncoding.UTF8);
try
try
l_Response := IdHTTP2.Post('https://securetoken.googleapis.com/v1/token?key=XXX, l_JsonToSend);
l_IRESAccessToken := TJson.JsonToObject<TIRESAccessToken>(l_Response);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
l_JsonToSend.Free;
end;
end;

Related

Execute in delphi a curl code for connection to stripe payment

I try to connect me to my stripe payment test account with delphi.
The connect API is here:
Stripe connect API
Curl example:
curl https://api.stripe.com/v1/charges \
-u sk_test_CpkBxhx9gcmNYYQTZIXU43Bv:
I tried using Indy TIdHTTP component with TIdSSLIOHandlerSocketOpenSSL
and calling post with Tstringlist or TIdMultipartFormDataStream as parameter
but I receive always response: 401 - Unauthorized
Here my code:
var
Data: TIdMultipartFormDataStream;
https: TIdHTTP;
ssl: TIdSSLIOHandlerSocketOpenSSL;
begin
https := TIdHTTP.Create(self);
ssl := TIdSSLIOHandlerSocketOpenSSL.Create(Self);
https.IOHandler := ssl;
https.Request.BasicAuthentication := True;
Data := TIdMultipartFormDataStream.Create;
//Data.AddFormField('api_key', 'sk_test_CpkBxhx9gcmNYYQTZIXU43Bv');
Data.AddFormField('apikey', 'sk_test_CpkBxhx9gcmNYYQTZIXU43Bv');
https.Post('https://api.stripe.com/v1/charges', Data);
Memo1.lines.Add( https.ResponseText );
Data.Free;
end;
Any help or suggestion would bee very appreciated.
Thanks,
Peter
You must not use a form field to transfer the API key. Instead, set the Request.Username property. The password is empty, so Request.Passwort is unused. From the API docs on your linked page:
Authentication to the API occurs via HTTP Basic Auth. Provide your API
key as the basic auth username. You do not need to provide a password.
This example works with Indy 10.6.2 and OpenSSL libraries in the program folder:
program Project31229779;
{$APPTYPE CONSOLE}
uses
IdHTTP, SysUtils;
var
HTTP: TIdHTTP;
begin
HTTP := TIdHTTP.Create;
try
HTTP.Request.BasicAuthentication := True;
HTTP.Request.Username := 'sk_test_CpkBxhx9gcmNYYQTZIXU43Bv';
try
WriteLn(HTTP.Get('https://api.stripe.com/v1/charges'));
except
on E: EIdHTTPProtocolException do
begin
WriteLn(E.Message);
WriteLn(E.ErrorMessage);
end;
on E: Exception do
begin
WriteLn(E.Message);
end;
end;
finally
HTTP.Free;
end;
ReadLn;
end.
Note: you may also put the user name / password in the URL:
HTTP.Request.BasicAuthentication := True;
try
WriteLn(HTTP.Get('https://sk_test_CpkBxhx9gcmNYYQTZIXU43Bv:#api.stripe.com/v1/charges'));

Login to craigslist to retrieve account page using Delphi and Indy and SSL

I am trying to log into craigslist using Delphi, and retrieve my account page (in order to gather a listing of all my posts)
However, I can't seem to get the login to work, what Am I doing wrong?
function TfrmMain.Login: string;
var
IdHTTP: TIdHTTP;
Request: TStringList;
Response: TMemoryStream;
begin
Result := '';
try
Response := TMemoryStream.Create;
try
Request := TStringList.Create;
try
Request.Add('op=login');
Request.Add('redirect=http://newyork.craigslist.org/');
Request.Add('login=' + myEmail);
Request.Add('password=' + myPassword);
IdHTTP := TIdHTTP.Create;
try
IdHTTP.AllowCookies := True;
IdHTTP.HandleRedirects := True;
IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
IdHTTP.Post('https://accounts.craigslist.org/login', Request, Response);
Result := IdHTTP.Get('https://accounts.craigslist.org/');
finally
IdHTTP.Free;
end;
finally
Request.Free;
end;
finally
Response.Free;
end;
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
I get a exception class EIdIOHandlerPropInvalid with message 'IOHandler value is not valid' on the line:
IdHTTP.Post('https://accounts.craigslist.org/login', Request, Response);
thanks
See (Indy) TIdHTTP EIdSocketError Socket Error # 0 exceptions when downloading files and the suggestions in the comments. It looks like you should upgrade to a more recent version of Indy.

Delphi: Using Google URL Shortener with IdHTTP - 400 Bad Request

I'm trying to access the URL Shortener ( http://goo.gl/ ) via its API from within Delphi.
However, the only result I get is: HTTP/1.0 400 Bad Request (reason: parseError)
Here is my code (on a form with a Button1, Memo1 and IdHTTP1 that has IdSSLIOHandlerSocketOpenSSL1 as its IOHandler. I got the necessary 32-bit OpenSSL DLLs from http://indy.fulgan.com/SSL/ and put them in the .exe's directory):
procedure TFrmMain.Button1Click(Sender: TObject);
var html, actionurl: String;
makeshort: TStringList;
begin
try
makeshort := TStringList.Create;
actionurl := 'https://www.googleapis.com/urlshortener/v1/url';
makeshort.Add('{"longUrl": "http://slashdot.org/stories"}');
IdHttp1.Request.ContentType := 'application/json';
//IdHTTP1.Request.ContentEncoding := 'UTF-8'; //Using this gives error 415
html := IdHTTP1.Post(actionurl, makeshort);
memo1.lines.add(idHTTP1.response.ResponseText);
except on e: EIdHTTPProtocolException do
begin
memo1.lines.add(idHTTP1.response.ResponseText);
memo1.lines.add(e.ErrorMessage);
end;
end;
memo1.Lines.add(html);
makeshort.Free;
end;
Update: I have left off my API key in this example (should usually work well without one for a few tries), but if you want to try it with your own, you can substitute the actionurl string with
'https://www.googleapis.com/urlshortener/v1/url?key=<yourapikey>';
The ParseError message leads me to believe that there might be something wrong with the encoding of the longurl when it gets posted but I wouldn't know what to change.
I've been fuzzing over this for quite a while now and I'm sure the mistake is right before my eyes - I'm just not seeing it right now.
Any help is therefore greatly appreciated!
Thanks!
As you discovered, the TStrings overloaded version of the TIdHTTP.Post() method is the wrong method to use. It sends an application/x-www-form-urlencoded formatted request, which is not appropriate for a JSON formatted request. You have to use the TStream overloaded version of the TIdHTTP.Post() method instead`, eg:
procedure TFrmMain.Button1Click(Sender: TObject);
var
html, actionurl: String;
makeshort: TMemoryStream;
begin
try
makeshort := TMemoryStream.Create;
try
actionurl := 'https://www.googleapis.com/urlshortener/v1/url';
WriteStringToStream(makeshort, '{"longUrl": "http://slashdot.org/stories"}', IndyUTF8Encoding);
makeshort.Position := 0;
IdHTTP1.Request.ContentType := 'application/json';
IdHTTP1.Request.Charset := 'utf-8';
html := IdHTTP1.Post(actionurl, makeshort);
finally
makeshort.Free;
end;
Memo1.Lines.Add(IdHTTP1.Response.ResponseText);
Memo1.Lines.Add(html);
except
on e: Exception do
begin
Memo1.Lines.Add(e.Message);
if e is EIdHTTPProtocolException then
Memo1.lines.Add(EIdHTTPProtocolException(e).ErrorMessage);
end;
end;
end;
From the URL shortener API docs:
Every request your application sends to the Google URL Shortener API
needs to identify your application to Google. There are two ways to
identify your application: using an OAuth 2.0 token (which also
authorizes the request) and/or using the application's API key.
Your example does not contain code for OAuth or API key authentication.
To authenticate with an API key, the docs are clear:
After you have an API key, your application can append the query
parameter key=yourAPIKey to all request URLs.

Download CSV in Delphi 5 with Indy

I know there's alot of Indy threads but I can't get one to match my case.
I have been given a URL with a username and password form. this then actions to a URL/reports.php on which there are multiple hyperlinks.
Each of these links will direct to a page with URL variables e.g. reports.php?report=variablename where a download will immediately start.
My thinking so far:
procedure TForm1.PostData(Sender: TObject);
var
paramList:TStringList;
url,text:string;
// IdHTTP1: TIdHTTP;
IdSSLIOHandlerSocket1: TIdSSLIOHandlerSocket;
idLogFile1 : TidLogFile;
begin
idLogFile1 := TidLogFile.Create(nil);
with idLogFile1 do
begin
idLogFile1.Filename := 'C:\HTTPSlogfile.txt';
idLogFile1.active := True;
end;
IdHTTP1 := TIdHTTP.Create(nil);
IdSSLIOHandlerSocket1 := TIdSSLIOHandlerSocket.Create(nil);
IdSSLIOHandlerSocket1.SSLOptions.Method := sslvSSLv23;
IdHTTP1.IOHandler := IdSSLIOHandlerSocket1;
IdHTTP1.HandleRedirects := true;
IdHTTP1.ReadTimeout := 5000;
IdHTTP1.Intercept := idLogFile1;
paramList:=TStringList.create;
paramList.Clear;
paramList.Add('loguser=testuser');
paramList.Add('logpass=duke7aunt');
paramList.Add('logclub=8005');
url := 'https://www.dfcdata.co.uk/integration/reports.php?report=live';
try
IdHTTP1.Post(url,paramList);
except
on E:Exception do
begin
showMessage('failed to post to: '+url);
ShowMessage('Exception message = '+E.Message);
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
reportType : String;
begin
PostData(Self);
reportType := 'live';
GetUrlToFile('',reportType+'.csv');
end;
procedure TForm1.GetUrlToFile(AURL, AFile : String);
var
Output : TMemoryStream;
success : Boolean;
begin
success := True;
Output := TMemoryStream.Create;
try
try
IdHTTP1.Get(AURL, Output);
IdHTTP1.Disconnect;
except
on E : Exception do
begin
ShowMessage('Get failed to GET from '+IdHTTP1.GetNamePath +'. Exception message = '+E.Message);
success := False;
end;
end;
if success = True then
begin
showMessage('Filed saved');
Output.SaveToFile(AFile);
end;
finally
Output.Free;
end;
end;
On each try I get "IOHandler is not valid" error. Obviously I'm not posting correctly to the initial page but can anyone advise me on what I'm missing? Also can I simply then hit the download URL after login or will I have to use cookies?
Thanks
There are several bugs in your code:
1) PostData() is requesting an HTTPS URL, but it is not assigning an SSL-enabled IOHandler to the TIdHTTP.IOHandler property. You need to do so.
2) Button1Click() is passing a URL to GetUrlToFile() that does not specify any protocol, so TIdHTTP will end up treating that URL as relative to its existing URL, and thus try to download from https://www.testurl.com/test/testurl.com/test/reports.phpinstead of https://testurl.com/test/reports.php. If you want to request a relative URL, don't include the hostname (or even the path in this case, since you are sending multiple requests to the same path, just different documents).
3) you are leaking the TIdHTTP object.
Issue 1) has now been resolved in another post:
Delphi 5 Indy/ics SSL workaround?
However I would greatly appreciate help on the rest, as follows.
Would I need to make a GET call with the same IdHTTP object and additional URL variable? or should I create a new IdHTTP object?
Would I need to record the session using cookies or can all of this be done with the same call?
Is the GET call above actually what I need to save a csv to file? I may also choose to handle it directly as the data will need importing anyway.
Currently the code gets the error: EIdHTTPProtocolException

Getting Response from TIdHttp with Error Code 400

I have been writing a Delphi library for StackApps API.
I have run into a problem with Indy. I am using the version that ships with Delphi 2010.
If you pass invalid parameters to one of the StackApps API it will return a HTTP Error Code 400 and then in the response it will contain a JSON object with more details.
By visiting http://api.stackoverflow.com/0.8/stats/?Key=BadOnPurpose in Chrome Browser you can see an Example. I.E. and Firefox hide the JSON.
Using WireShark I can see that the JSON object is returned using the code below, but I am unable to access it using Indy.
For this test code I dropped a TIdHttp component on the form and placed the following code in a button click.
procedure TForm10.Button2Click(Sender: TObject);
var
SS : TStringStream;
begin
SS := TStringStream.Create;
IdHTTP1.Get('http://api.stackoverflow.com/0.8/stats/?Key=BadOnPurpose',SS,[400]);
Memo1.Lines.Text := SS.DataString;
SS.Free;
end;
I passed [400] so that it would not raise the 400 exception. It is as if Indy stopped reading the response. As the contents of Memo1 are empty.
I am looking for a way to get the JSON Details.
Remove the AIgnoreReplies parameter value from your call to Get(). Let it raise the exception normally. The JSON text you are looking for is in the EIdHTTPProtocolException.ErrorMessage property. For example:
procedure TForm10.Button2Click(Sender: TObject);
begin
try
Memo1.Lines.Text := IdHTTP1.Get('http://api.stackoverflow.com/0.8/stats/?Key=BadOnPurpose');
except
on E: EIdHTTPProtocolException do begin
if E.ErrorCode = 400 then
Memo1.Lines.Text := E.ErrorMessage
else
raise;
end;
end;
end;

Resources