I am trying to insert an event into my Google Calendar via the Delphi REST controls, but I am uncertain where to add the access token to my request. My Code looks like this:
var
evt : String;
begin
ResetRESTComponentsToDefaults;
RESTClient.BaseURL := 'https://www.googleapis.com/calendar/v3';
RESTClient.Authenticator := OAuth2_GoogleCalendar;
RESTRequest.Resource := 'calendars/primary/events';
evt:='{"summary":"test","description":"test","id":"06824945162f4204bfdc041ae1bbae85","start":{"date":"2018-04-10"},"end":{"date":"2018-04-10"},"guestsCanInviteOthers":false,"visibility":"private"}'
RESTRequest.AddParameter('access_token',OAuth2_GoogleTasks.AccessToken,pkHTTPHEADER);
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Body.Add(evt,ctAPPLICATION_JSON);
RESTRequest.Execute;
end;
The scope is https://www.googleapis.com/auth/calendar
If I send it in like this I this error:
{
"error":
{
"errors":
[
{
"domain":"global",
"reason":"required",
"message":"Login Required",
"locationType":"header",
"location":"Authorization"
}
]
,
"code":401,
"message":"Login Required"
}
}
Appending ?access_token={accessToken} to the end of the url i get
error 400, parseError.
Where should I add the access token to the request?
I cant help much with Delphi its been years since i have used it, but you have a two options with regard to adding your access token.
First is to just add it as a parameter on the base url
?access_token=TokenHere
The second option is to send it as an authorization header on your request its a bearer token.
Authorization : Bearer cn389ncoiwuencr
After a bit of googling i found this
FIdHTTP.Request.CustomHeaders.FoldLines := False;
FIdHTTP.Request.CustomHeaders.Add('Authorization:Bearer ' + txtToken.Text);
Related
I'm having trouble with a POST request to an API which I am not the owner of.
The request must simply post JSON data. Please have a look:
var
RESTRequest : TRESTRequest;
RESTClient : TRESTClient;
Response : TRESTResponse;
contract : TJSONObject;
begin
RESTClient := TRESTClient.Create('URL');
try
RESTRequest := TRESTRequest.Create(nil);
try
contract := TJSONObject.Create;
contract.AddPair(TJSONPair.Create('name','my_first_contract.pdf'));
RESTRequest.Client := RESTClient;
RESTRequest.Method := rmPOST;
RESTRequest.Accept := 'application/json';
RESTRequest.AddParameter('j_token','mytoken',pkHTTPHEADER,poDoNotEncode);
RESTRequest.AddBody(contract);
RESTRequest.Execute;
Response := RESTRequest.Response;
ShowMessage(Response.StatusText + ' : ' + Response.Content);
finally
RESTRequest.Free;
end;
finally
RESTClient.Free;
end;
end;
I obtained this error :
Not Found : {"errors":"Fatal error in JsonConvert. Passed parameter json object in JsonConvert.deserializeObject() is not of type object.\n"}
I've read online that the AddBody() method first serializes its content if it's an object. In this case, the content of the body is my TJSONObject, but when I try to replace that with a String, like this:
var
contract : String;
...
begin
contract := '{"name":"my_first_contract.pdf"}';
...
RESTRequest.AddBody(contract, ctAPPLICATION_JSON);
...
end;
I'm getting the exact same error.
So, does that mean that a TJSONObject is not viewed as an Object for the JsonConvert.deserializeObject() method ? Or, is the serialization of the AddBody() messed up?
The problem was on the 'j_token' header : as I was trying to solve it, some friends wanted to help me but I didn't want to give them the access token because it's exclusive to my company. They still tried to access the api with a false token wich resulted with the same error as I was getting :
Not Found : {"errors":"Fatal error in JsonConvert. Passed parameter json object in JsonConvert.deserializeObject() is not of type object.\n"}
Thanks to that I could deduce that the issue was on the j_token. After setting up my own api I could watch what was I posting and then I saw that my 'j_token' header was still getting encoded even though I added the poDoNotEncode options to my AddParameter method.
I created a new post on this forum to look for that poDoNotEncode error if you ever stumble upon this same problem : Trouble with poDoNotEncode option in TRESTRequest.AddParameter() method
No authentication is required to call this REST service that was created with Delphi.
The client Classes API documented (ClientClassesUnit1.pas).
function TGeneralClient.UpdateCIM2(PostData : TJSONOjbect; const ARequestFilter:string):string
begin
if FUpdateCIM2Command = nil then
begin
FUpdateCIM2Command := FConnection.CreateCommand;
FUpdateCIM2Command.RequestType := 'POST';
FUpdateCIM2Command.Text := 'TGeneral."UpdateCIM2"';
FUpdateCIM2Command.Prepare(TGeneral_UpdteCIM2);
end;
FUpdateCIM2Command.Parameters[0].Value.SetJSONValue(PostData,FInstanceOwner);
FUpdateCIM2Command.Execute(ARequestFilter);
Result := FUpdateCIM2Command.Parmeters[1].Value.GetWideString;
end;
But I would like to create a test using the Delphi REST Debugger and I cannot determine what the settings in the utility should be ... or even if the REST Debugger is capable of doing it. Postman tool doesn't seem to work either.
I tried to post a picture but can't because of company policy restrictions.
Method: POST
URL: https://wxdf0-servername.com/snap4.dll/datasnap/rest/TGeneral/UpdatCIM2
Content type: application/json
Custom body (edited)
{
"data" :{
"Stage" :"7",
"Step" :"1"
}
}
All I get in return on the debugger is "500 :Internal Server error"
What is the proper setup (if possible) to make a call to a Delphi REST call that uses TJSONObject as a parameter?
An object in JSON is surround by { } which you have omitted. As well, properties are separated by a comma.
{
"data": {
"Stage": "7",
"Step": "1"
}
}
I am trying to insert an event into my google calendar using the delphi REST controls.
This is the code so far:
procedure TForm1.TestGoogleRestParams;
var
i: Integer;
jsonObjEventResource,jsonObjStart,jsonObjEnd: TJSONObject;
begin
try
jsonObjEventResource:=TJSONObject.Create();
jsonObjStart:=TJSONObject.Create();
jsonObjEnd:=TJSONObject.Create();
jsonObjEventResource.AddPair(TJSONPair.Create('summary','test'));
jsonObjEventResource.AddPair(TJSONPair.Create('description','Testing'));
jsonObjEventResource.AddPair(TJSONPair.Create('id',LowerCase('06824945162F4204BFDC041AE1BBAE85')));
jsonObjStart.AddPair(TJSONPair.Create('date',FormatDateTime('yyyy-mm-dd',Now)));
jsonObjEventResource.AddPair(TJSONPair.Create('start',jsonObjStart));
jsonObjEnd.AddPair(TJSONPair.Create('date',FormatDateTime('yyyy-mm-dd',Now)));
jsonObjEventResource.AddPair(TJSONPair.Create('end',jsonObjEnd));
jsonObjEventResource.AddPair(TJSONPair.Create('guestsCanInviteOthers',TJSONBool. Create(false)));
jsonObjEventResource.AddPair(TJSONPair.Create('visibility','private'));
mem_Test.Lines.Add(TJson.Format(jsonObjEventResource));
//mem_Test.Lines.Add(jsonObjEventResource.ToJSON);
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Body.ClearBody;
RESTRequest.AddBody(jsonObjEventResource);
RESTRequest.Execute;
finally
//jsonObjEventResource.Free;
//jsonObjStart.Free;
//jsonObjEnd.Free;
end;
end;
The Scope I am using is: https://www.googleapis.com/auth/calendar.
BaseURL : https://www.googleapis.com/calendar/v3
ResourceURI : calendars/primary/events
I do get an access token and a refresh token but I cannot Post the Request. This is the error i recieve:
{
"error":
{
"errors":
[
{
"domain":"global",
"reason":"required",
"message":"Login Required",
"locationType":"header",
"location":"Authorization"
}
]
,
"code":401,
"message":"Login Required"
}
}
With the following uri: https://www.googleapis.com/calendar/v3/calendars/primary/events
How can i fix this?
If I don't call this method and just call RESTRequest.Execute; I get a list of all my existing events.
":"Invalid Credentials"
Means that the client id and client secret that you are using are incorrect. You should download a new file from Google Developer console and try again.
Option 2:
Try authenticating the user it could be that the access token has expired and needs to be refreshed
I must be missing something here!
I have been playing around trying to refresh an expired OAUTH2 token using the new ( new to me anyway, coming from delphi xe2 environment) TOAuth2Authenticator, TRESTClient, TRESTRequest, TRESTResponse components
I have set the following authenticator properties with the existing known values for
ClientID
ClientSecret
Scope
AccessTokenEndPoint
AuthorizationEndPoint
RedirectionEndPoint
AccessToken
AccessTokenExpiry
RefreshToken
and can successful access resources from the REST server, up until the token expires.
I presumed (wrongly, so it seems) if I try an execute a request against a server, and the token had expired, there should be enough detail for the component to realise the token has expired and refresh it as and when it need to.
I take it there is no hidden/undocumented "RefreshExpiredToken" method that I can call?
Any pointers in the right direction would be greatly appreciated :-)
Thanks
I eventually figured this out, by bastardising the publicTOAuth2Authticator.ChangeAuthCodeToAccessToken procedure, but thought I'd post my solution just in case it helps anyone else out:
LClient := TRestClient.Create(AccessTokenURI);
try
LRequest := TRESTRequest.Create(LClient); // The LClient now "owns" the Request and will free it.
LRequest.Method := TRESTRequestMethod.rmPOST;
LSecretBase64 := String(SZFullEncodeBase64(AnsiString(<myClientID>+ ':' + <MyClientSecret>)));
LRequest.AddAuthParameter('grant_type', 'refresh_token', TRESTRequestParameterKind.pkGETorPOST);
LRequest.AddAuthParameter('refresh_token', _AccessRefreshToken, TRESTRequestParameterKind.pkGETorPOST);
LRequest.AddAuthParameter('Authorization','Basic '+LSecretBase64, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode] );
LRequest.Execute;
//Memo1.Lines.Add(LRequest.Response.Content);
if LRequest.Response.GetSimpleValue('access_token', LToken) then
begin
_AccessToken := LToken;
end;
if LRequest.Response.GetSimpleValue('refresh_token', LToken) then
begin
_AccessRefreshToken := LToken;
//Memo1.Lines.Add('RefreshExpiredToken: New Refresh Token Extracted');
end;
// detect token-type. this is important for how using it later
if LRequest.Response.GetSimpleValue('token_type', LToken)
then _TokenType := OAuth2TokenTypeFromString(LToken);
// if provided by the service, the field "expires_in" contains
// the number of seconds an access-token will be valid
if LRequest.Response.GetSimpleValue('expires_in', LToken) then
begin
LIntValue := StrToIntdef(LToken, -1);
if (LIntValue > -1) then
_AccessTokenExpireDT := IncSecond(Now, LIntValue)
else
_AccessTokenExpireDT := 0.0;
//Memo1.Lines.Add('RefreshExpiredToken: New Token Expires '+formatdatetime('hh:nn:ss dd/mm/yyyy', _AccessTokenExpireDT));
end;
finally
LClient.DisposeOf;
end;
I'm implementing a REST client application which communicates with the Coinbase GDAX Trading API in the JSON format (www.coinbase.com and https://docs.gdax.com/#introduction). I'm facing a problem in signing REST request messages with POST. Signing REST request messages with GET is working fine. Mainly because GET messages don't have a body parameter, which would be part of the signature when available. And this body parameter drives me crazy :-) I'm struggling to get the JSON string stored in the body parameter of a REST request (TCustomRESTRequest.Body). I need this JSON string to sign the REST request message properly. If I pass the body JSON string outside of TCustomRESTRequest.Body (ugly workaround), I get HTTP error 400 (bad request) with additional information "invalid signature". I assume the JSON string in TCustomRESTRequest.Body is somehow altered from the original JSON string. What I would like to achieve is to read out the JSON string directly from TCustomRESTRequest.Body, and then do the signing.
All the authentication and signing I'm doing in my class TCoinbaseAuthenticator (which is inherited from TCustomAuthenticator), or more specific in the DoAuthenticate method:
procedure TCoinbaseAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest);
var
DateTimeUnix: Int64;
DateTimeUnixStr: string;
Sign: string;
HttpMethod: string;
BodyStr: string;
begin
inherited;
ARequest.Params.BeginUpdate;
try
DateTimeUnix := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(Now));
DateTimeUnixStr := IntToStr(DateTimeUnix);
HttpMethod := HttpMethodToString(ARequest.Method);
// BodyStr := ARequest.Body ... << here I'm strugging to get the JSON string
Sign := GenerateSignature(DateTimeUnixStr, HttpMethod, '/' + ARequest.Resource, BodyStr);
ARequest.AddAuthParameter('CB-ACCESS-KEY', FAPIKey, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-ACCESS-SIGN', Sign, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-ACCESS-TIMESTAMP', DateTimeUnixStr, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-ACCESS-PASSPHRASE', FAPIPassphrase, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-VERSION', '2015-07-22', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('Content-Type', 'application/json', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
finally
ARequest.Params.EndUpdate;
end;
end;
Here is my signature generating function (I think the code is ok, because it works fine if I don't have to consider the body parameter, e.g. for GET requests):
function TCoinbaseAuthenticator.GenerateSignature(ATimeStamp: string; AMethod: string; AURL: string; ABody: string): string;
var
s: string;
SignStr: string;
BitDigest: T256BitDigest;
key: string;
begin
s := ATimeStamp+AMethod+AURL+ABody;
key := MimeDecodeString(FAPISecret);
BitDigest := CalcHMAC_SHA256(key, s);
SignStr := SHA256DigestAsString(BitDigest);
SignStr := MimeEncodeStringNoCRLF(SignStr);
Result := SignStr;
end;
Some more insights of the Coinbase (GDAX) message signing process:
All REST requests must contain the following headers:
CB-ACCESS-KEY: The api key as a string (created by Coinbase)
CB-ACCESS-SIGN: The base64-encoded signature
CB-ACCESS-TIMESTAMP: A timestamp for your request
CB-ACCESS-PASSPHRASE: The passphrase you specified when creating the API key
The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp + method (GET,POST, etc.) + requestPath + body (where + represents string concatenation) and base64-encode the output. The timestamp value is the same as the CB-ACCESS-TIMESTAMP header.
The body is the request body string or omitted if there is no request body (typically for GET requests).
Thanks a lot for any help!
Thanks a lot for your comment, Remy! I did indeed use the wrong charset for the signature calculation. So at least my super ugly workaround (passing the body JSON string outside of TCustomRESTRequest.Body into the authenticator class) is working.
For the first part of the problem I don't think there is an easy solution. I got some support from Embarcadero which I would like to share with others who have a similar problem.
Looking at the TBody class, which is used for the Body property, this looks like it only has properties to write to - there isn't anything that will help in reading it back. I found that if I added a body to a TRequest, this created a TRestRequestParameter with a name of body. I would therefore suggest that you can get to it via RestRequest1.Params.ParameterByName('body');
Unfortunately this proposal is not working. Reason:
The parameter "body" is stored in the body request parameter list (TCustomRESTRequest.FBody.FParams), and not in RESTRequest parameter list (TCustomRESTRequest.Params). Since TCustomRESTRequest.FBody.FParams doesn't have a public property, I cannot access the field from outside. Later the parameters are copied from TCustomRESTRequest.FBody.FParams to TCustomRESTRequest.Params, but this is too late in my case, because the authenticator (where I calculate the signature) is called earlier. Meaning in my TCoinbaseAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest) method the parameter list in ARequest is still empty.