How to return ReportCloud API response into a downloadable file? - coldfusion

I am using ColdFusion to call the ReportCloud API which does a mail merge on the fly based on parameters I send it via cfhttp.
The response from the documentation says:
On success, the HTTP status code in the response header is 200 (OK). The response body contains an array of the created documents encoded as Base64 encoded strings.
Can someone help me with how to turn that response into a downloadable file, either a link or just a straight download? I probably should know this but I don't unfortunately.
Added comments:
Thanks for your help and guidance with this question. I'm using StackOverflow as a poster for the first time, my apologies if the question is vague.
I have put together a code example also to view cffiddle
<!--- Must replace "Authorization" header below with a real key --->
<cfset variables.jsonReq = '{
"mergeData": [
{
"Given_Name": "Mike",
"Surname": "Smith",
"Year_Group": "11"
},
{
"Given_Name": "Sally",
"Surname": "Smith",
"Year_Group": "12"
}
],
"template": null,
"mergeSettings": null
}'>
<cfhttp url="https://api.reporting.cloud/v1/document/merge?returnFormat=DOC&templateName=parentletter.docx" method="post" timeout="20" result="response" file="/www/something.docx">
<cfhttpparam type="header" name="Content-Type" value="application/json">
<cfhttpparam type="header" name="Authorization" value="ReportingCloud-APIKey oMDM4MrAqL9QEOpyzupnQW5NjvCNtvE5cVDaaLqxI">
<cfhttpparam type="body" name="mergeData" value="#jsonReq#">
</cfhttp>
<cfdump var="#response#">
The response is coming back in what I think is a JSON array. I'm not sure how to go about reading that JSON array and making the contents into a downloadable file.

Related

Cfhttp and mailgun api

I’ve been trying to configure Mailgun API with Coldfusion's Cfhttp and can’t figure out the format to pass the variables. I configured it in about 5 minutes with Python, and have to roll it to a Coldfusion front end. I’ve used the same basic structure as I did in Python, and it all works great in Python.
I’ve been able to make a connection, but I have not been able to send mail. I know it has to do with the structure, and I can’t find a working example anywhere.
How do you build the cfhttp to pass the required data? Is the data=data I would use in the request in Python the same as a cfhttpparam in ColdFusion? And is it a header, or a body?
My Coldfusion code is below, i've replaced secret information with domain.com
<cfscript>
mailgun_variables = '{
"company_id": 1008,
"company_contact": "Joe Smith",
"random_key": "91A303C8-91FB-AA1D-DE07F18782594721"
}';
data= '{
"from": "email#domain.com",
"to": "email#domain.com",
"subject": "Subject line",
"template": "welcome",
"o:tag": ["welcome", "welcome-countdown", "welcome-alt", "coldfusion"],
"h:X-Mailgun-Variables": #mailgun_variables#}';
</cfscript>
<cfhttp url="https://api.mailgun.net/v3/domain.com/messages"
method="POST"
username="api"
password="APIKEY"
result="response" >
<cfhttpparam type="header" name="Content-Type" value="application/json" />
<cfhttpparam type="header" name="data" value="#serializeJSON(data)#">
</cfhttp>
My error message is a 400 Bad request, {"message":"from parameter is missing"}.
Any guidance or links to examples would be much appreciated. Or maybe you can obviously spot what I am doing wrong?
I ended up having to do a lot of trial and error, but have working code if anyone else ever runs into this issue. Mailgun support was no help.
The answer ended up being that everything had to be passed as cfhttpparam formfields. I also ran into a lot of challenges with tagging and using template variables, but got it all worked out.
I added it to Github for anyone to use, and will continue to improve it there as I get more in-depth with it.
https://github.com/convurt/Mailgun-Coldfusion-Sendmail
cfhttp(method="POST", url="https://api.mailgun.net/v3/DOMAIN.COM/messages", result="response", username="api", password="MAILGUN API KEY") {
cfhttpparam(name="from", type="formfield", value="#email_from#");
cfhttpparam(name="h:Reply-To", type="formfield", value="#reply_to#");
cfhttpparam(name="to", type="formfield", value="#email_to#");
cfhttpparam(name="subject", type="formfield", value="#email_subject#");
// loop over tag list, I could not get it to work correctly as a list
for (t in tags) {
cfhttpparam(name="o:tag", type="formfield", value="#t#");
}
cfhttpparam(name="template", type="formfield", value="#template#");
// custom paramamter for template
cfhttpparam(name="t:variables", type="formfield", value="#t_variables#");

coldfusion get API token from salesforce - invalid grant

I'm having a terrible time...I am trying to get coldfusion to get a token from salesforce, but I am stuck on the following error
{"error":"invalid_grant","error_description":"authentication failure"}
ive tried cfscript
local.http = new Http(url='https://test.salesforce.com/services/oauth2/token',method='post');
local.http.addParam(type='header',name='content-type', value='application/x-www-form-urlencoded');
local.http.addParam(type='formField',name='grant_type', value='password');
local.http.addParam(type='formField',name='client_id', value='client_id');
local.http.addParam(type='formField',name='client_secret', value='client_password_string');
local.http.addParam(type='formField',name='username', value='user#email.com');
local.http.addParam(type='formField',name='password', value='userspassword');
local.http.addParam(type='formField',name='format', value='json');
local.httpSendResult = local.http.send();
rc.httpResult = httpSendResult.getPrefix();
ive tried cfhttp tags
<cfhttp url="https://test.salesforce.com/services/oauth2/token" method="POST">
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded" />
<cfhttpparam type="formField" name="grant_type" value="password" />
<cfhttpparam type="formField" name="client_id" value="client_id" />
<cfhttpparam type="formField" name="client_secret" value="client_password_string" />
<cfhttpparam type="formField" name="username" value="user#email.com" />
<cfhttpparam type="formField" name="password" value="userspassword" />
</cfhttp>
<cfset rc.result = cfhttp.fileContent />
however the same exact call executed by cUrl on my local machine works perfectly fine
curl -d "grant_type=password"
-d "client_id=client_id"
-d "client_secret=client_secret_string"
-d "username=user#email.com.dev"
-d "password=userpassword" https://test.salesforce.com/services/oauth2/token
ive made sure that my ip range is whitelisted, my ip relaxation is set to relax, allusers may self authorize, ive tried different username and password parameters, all i get is the same error about invalid grants
any help is very appreciated
I am not able to comment, so I have to answer :) But this is really just a suggestion. When you use <cfhttpparam type="formfield">, by default ColdFusion URL encodes that for you. ColdFusion will encode the characters ~, ., -, and _, but it should not actually do that according to the RFC 3986 spec (see https://en.wikipedia.org/wiki/Percent-encoding), since they are unreserved characters. If your formfields contain those characters (and I suspect they do since your example shows an email address), it is possible that those characters being encoded incorrectly is what is causing the authentication failure.
As a quick test you could add encoded="false" to your <cfhttpparam> tags and then url encode their values yourself using either encodeForUrl() or urlEncodedFormat() (depending on your version of ColdFusion), and then undo the incorrect encoding:
<cfhttpparam type="formField" encoded="false" name="username" value="#replacelist(urlEncodedFormat('user#email.com'), '%2D,%2E,%5F,%7E', '-,.,_,~')#">
Below is some CFScript code pulled out of a CFC I created for accessing salesforce. (won't run as is, but the logic is there - sorry it was too much code to copy the complete thing). Hoping it will help.
I'm using predominantly the same approach as you, but you might want to steal some of the specific settings (charset, content type, accept).
I've faked the values in the loginCredentials struct, but they are roughly the same kind of format (to compare with your own).
This runs fine for us on Railo. Maybe also check that it's not a SSL issue - do you need to add the SSL certs into Coldfusion so it can talk to Salesforce?
variables.sfAuthDomain = "https://login.salesforce.com";
variables.authServiceURL = variables.sfAuthDomain & "/services/oauth2/token";
variables.accessToken = "xxxxx" // The access token returned by SF, used on future logins
variables.loginCredentials = {
"grant_type": "password",
"client_id": "3MVG9Fkjshdkfjvshd ckjfhjkch.blkjlkjlkjkljl.wkjhgkjhkjhds.mVk84TRzhm_pXxK6_786786",
"client_secret": "3887687686868668727",
"username": "huge.duck#monkey.com.icom.icomqa",
"password": "Bungerloo!PPkjhj324ij45bQGyymmd"
};
/**
* MAKE SERVICE CALL
* Makes HTTP service call
**/
public Struct function makeServiceCall(String serviceUrl, String method="GET", Boolean sfAuth=true, Struct headers={}, Struct formFields) {
var httpService = new http(); // create new http service
var httpResponse = {};
var fieldName = "";
var bodyData = "";
/* set attributes using implicit setters */
httpService.setMethod(arguments.method);
httpService.setCharset("utf-8");
httpService.setUrl(Trim(arguments.serviceURL));
httpService.setTimeOut(variables.timeoutValue);
/* add httpparams using addParam() */
for(fieldName in arguments.headers) {
httpService.addParam(type="header", name="#fieldName#", value="#arguments.headers[fieldName]#");
}
if(arguments.sfAuth){
httpService.addParam(type="header", name="Authorization", value="OAuth #variables.accessToken#");
}
if(StructKeyExists(arguments, "formFields")) {
loop collection="#arguments.formFields#" item="fieldName" {
bodyData = ListAppend(bodyData, "#fieldName#=#formFields[fieldName]#", "&");
}
}
if(bodyData is not "") {
httpService.addParam(type="body", name="post", encoded="no", value="#bodyData#");
}
/* make the http call to the URL using send() */
httpResponse = httpService.send().getPrefix();
//dump(httpResponse, false, "modern", "", 500, "httpResponse");
return httpResponse;
}
httpResponse = makeServiceCall(
serviceUrl = serviceURL,
method = "POST",
sfAuth = false,
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"accept": "text/json"
},
formFields = loginCredentials
);
Ended up changing my coldfusion server to use TLS 1.2, as TLS 1.0 was deprecated for use by salesforce. Hope my answer and the helpful answers given on this post help someone else
Your password formfield value is contain only password, so that kind of error is shown:
({"error":"invalid_grant","error_description":"authentication failure"}).
That must be contain(password and security token).
<cfhttpparam type="formField" name="password" value="#arguments.password##arguments.Security_token#"/>

POST JSON ignoring content-type

Using ColdFusion, I am trying to POST JSON to an API. Here is the code I have so far -
<cfhttp url="#url#" method="post" result="httpResp" timeout="60">
<cfhttpparam type="header" name="Content-Type" value="application/json" />
<cfhttpparam type="body" value="#serializeJSON(jsonStr)#">
</cfhttp>
An example of the JSON is here -
{
"booking":{
"username" : "#username#",
"password" : "#password#",
"customerEmail" : "#customer_email_address#",
"firstName" : "#customer_firstname#",
"lastName" : "#customer_surname#",
"telephoneNumber" : "#customer_mobile_number#",
"guestNumber" : #url.guests#,
"unitNumber" : #url.location#,
"eventDate" : "#LSDateFormat(url.when,'dd/mm/yyyy')#"
}
}
When I pass this JSON to the API URL with POSTMAN client in Chrome, everything is good! however when I process this in CF, I simply get a bad request error from the API. I realise that message is no use that is simple whats being set in the API.
If I remove the content-type from POSTMAN client in Chrome, I get the same message. So I am "assuming" That the content-type is not being sent or over written somehow in CF.
Can anyone point me in the right direction?
Thanks
Issue was with the API not accepting what I thought it required.

How do I get CFHTTP with file param to show only file name rather than full path?

We are trying to interact with a RESTful web service that expects a file.
I set the name of the field to data (as required by the API) and then specify the file as an absolute path. When the file makes it to the server, the filename in the HTTP transaction is the complete absolute path.
This causes a problem with the API as the full path is then recorded as the "FileName".
How do I get ColdFusion to report only the file name rather than the full path?
We are using ColdFusion 9.
Here is the CFML:
<cfhttp url="http://server/testcode"
port="9876"
method="post"
result="Content">
<cfhttpparam type="file"
name="data"
file="c:\temp\testfile.txt">
</cfhttp>
Here are some examples of the HTTP interactions with different browsers:
CFHTTP 9
-------------------------------7d0d117230764
Content-Disposition: form-data; name="data"; filename="c:\temp\testfile.txt"
Content-Type: text/plain
This is the text, really long, well, not really.
-------------------------------7d0d117230764--
IE8
-----------------------------7db370d80e0a
Content-Disposition: form-data; name="FileField"; filename="C:\temp\testfile.txt"
Content-Type: text/plain
This is the text, really long, well, not really.
-----------------------------7db370d80e0a--
Chrome 13
------WebKitFormBoundaryDnpFVJwCsZkzTGDc
Content-Disposition: form-data; name="FileField"; filename="testfile.txt"
Content-Type: text/plain
This is the text, really long, well, not really.
Firefox 6
-----------------------------22798303036224
Content-Disposition: form-data; name="FileField"; filename="testfile.txt"
Content-Type: text/plain
This is the text, really long, well, not really.
-----------------------------22798303036224--
Apparently IE8 and CFHTTP both do the same thing (add "c:\temp" to the file name). I'm not sure what the spec for HTTP is, but it would be nice if there was a way to get CFHTTP to leave the path off.
Is there any way to do this?
I ran into a problem similar to yours, once. I didn't care about excluding the path, but I wanted to send a different filename than the name of the file on my server's filesystem. I could not find a way to do it using CF tags at all, but I was able to get it to work by dropping into Java. I used org.apache.commons.httpclient, which ships with CF9 IIRC. It goes something like this (pardon any typos, I'm transposing from more complicated code):
oach = 'org.apache.commons.httpclient';
oachmm = '#oach#.methods.multipart';
method = createObject('java', '#oach#.methods.PostMethod').init(post_uri);
filePart = createObject('java', '#oachmm#.FilePart').init(
'fieldname',
'filename',
createObject('java', 'java.io.File').init('filepath')
);
method.setRequestEntity(
createObject('java', '#oachmm#.MultipartRequestEntity').init(
[ filePart ],
method.getParams()
)
);
status = createObject('java', '#oach#.HttpClient').init().executeMethod(method);
method.releaseConnection();
I see that the content type is text/plain so first I think that you need to add the multipart property on the CFHTTP
<cfhttp url="http://server/testcode"
port="9876"
method="post"
result="Content"
multipart = "yes">
<cfhttpparam type="file"
name="data"
file="c:\temp\testfile.txt">
</cfhttp>
Could solve your issue.
The only difference I see between all of the posts is that CF is sending name="data" while everything else is sending name="FileField". If the other browser submissions are correct, then I would change your cfhttpparam:
<cfhttpparam type="file"
name="FileField"
file="c:\temp\testfile.txt">
or even try sending an additional FileName parameter:
<cfhttpparam type="file"
name="data"
file="c:\temp\testfile.txt" />
<cfhttpparam type="formField"
name="FileName"
value="testfile.txt" />
So I was able to get access to the API and made it work. Here is the code for this specific part (as I assume that you were able to login and get a document guid).
<!--- upload a document --->
<cfhttp method="post" url="<path to watchdox api upload>/#local.guid#/upload">
<cfhttpparam type="header" name="Content-type" value="multipart/form-data">
<cfhttpparam type="header" name="x-wdox-version" value="1.0">
<cfhttpparam type="header" name="x-wdox-ssid" value="#local.xwdoxssid#" >
<cfhttpparam type="formfield" name="filename" value="testfile.txt" >
<cfhttpparam type="file" file="c:\temp\testfile.txt" name="data" >
</cfhttp>
Hope it will help.

Coldfusion, The oauth_signature is invalid

I'm trying to obtain credentials from ning network using Coldfusion 9, so first this is the curl syntax to test the api :
curl -k https://external.ningapis.com/xn/rest/mbdevsite/1.0/Token?xn_pretty=true -u devshare#megabase.tn:mbdev2011 -d "oauth_signature_method=PLAINTEXT&
oauth_consumer_key=741ab68b-63fb-4949-891c-9e88f5143034&oauth_signature=36da2ea8
-10fb-48cc-aaa4-c17c551c6b87%26"
and it returns :
{
"success" : true,
"entry" : {
"author" : "1o0butfek0b3p",
"oauthConsumerKey" : "741ab68b-63fb-4949-891c-9e88f5143034",
"oauthToken" : "46f1e137-549a-4d9d-ae05-62782debfd3d",
"oauthTokenSecret" : "9f778ab5-db8e-4f3e-b17f-61d249b91f0a"
},
"resources" : {
}
then i translated it to coldfusion like this :
<cfhttp
method="post"
url="https://external.ningapis.com/xn/rest/mbdevsite/1.0/Token"
username="devshare#megabase.tn"
password="mbdev2011">
<cfhttpparam type="header" name="content-type" value="application/x-www-form-urlencoded">
<cfhttpparam name="oauth_signature_method" type="FormField" value="PLAINTEXT"/>
<cfhttpparam name="oauth_consumer_key" type="FormField" value="741ab68b-63fb-4949-891c-9e88f5143034"/>
<cfhttpparam name="oauth_signature" type="FormField" value="36da2ea8-10fb-48cc-aaa4-c17c551c6b87%26"/>
</cfhttp>
<cfoutput>
#cfhttp.fileContent#
</cfoutput>
and the response is always :
{"success":false,"reason":"The oauth_signature is invalid. That is, it doesn't match the signature computed by the Service Provider.","status":401,"code":1,"subcode":12,"trace":"3d874587-072b-4877-b27e-b84ee2e2b537"}
does somebody have idea about what could be this error ??
url and login info are real for who wants to help by testing
Thank you..
Don't disclose your username & password in public forums. Better you change this user name & password after this issue completion :)
Your oauth_signature is 36da2ea8-10fb-48cc-aaa4-c17c551c6b87& not "36da2ea8-10fb-48cc-aaa4-c17c551c6b87%26"
I got the success response & it is working perfectly.
<cfhttp
method="post"
url="https://external.ningapis.com/xn/rest/mbdevsite/1.0/Token"
username="devshare#megabase.tn"
password="mbdev2011">
<cfhttpparam type="header" name="content-type" value="application/x-www-form-urlencoded">
<cfhttpparam name="oauth_signature_method" type="FormField" value="PLAINTEXT"/>
<cfhttpparam name="oauth_consumer_key" type="FormField" value="741ab68b-63fb-4949-891c-9e88f5143034"/>
<cfhttpparam name="oauth_signature" type="FormField" value="36da2ea8-10fb-48cc-aaa4-c17c551c6b87&"/>
</cfhttp>
Any specific reason why you're using cURL instead of cfhttp?
There's a nice library on RIAForge:
OAuth
that will help you with dealing with OAuth. The issue is probably with the parameters encoding.
Oh - and you shouldn't be posting your OAuth credentials.
UPDATE:
I'm afraid using OAuth isn't as simple as just calling cfhttp with params.
The parameters need to be in certain order, you need to sign the whole request using appropriate method (plain text in your case). The signing process also includes time stamp so you can't test your code with the values from the example as they definitely won't work.
If you download the RIAForge libraries there's an "\examples_external" folder there and twitter.cfm - you'll find all that I've mentioned there.
A bit of code from there to show what I mean:
<!--- Create empty token --->
<cfset oReq = CreateObject("component", "oauth.oauthrequest").fromConsumerAndToken(
oConsumer = oConsumer,
oToken = oToken,
sHttpMethod = "GET",
sHttpURL = sTokenEndpoint,stparameters= Parameters )>
<!--- Sign the request --->
<cfset oReq.signRequest(
oSignatureMethod = oReqSigMethodSHA,
oConsumer = oConsumer,
oToken = oToken)>
<!--- Get the request token --->
<cfhttp url="#oREQ.getString()#" method="get" result="tokenResponse"/>
Of course there's lots of bits missing before and after it.
You might check out Ben Nadel's blog post on OAuth. He covers some of the things you may be running into.