I'm attempting to get a Token from ETrade to start the authentication process, but I can't seem to get the signature to match what eTrade thinks it should be.
The Link to the ETrade API:
https://developer.etrade.com/ctnt/dev-portal/getDetail?contentUri=V0_Documentation-AuthorizationAPI-GetRequestToken
The Link to the OAuth Spec:
https://oauth.net/core/1.0a/#sig_base_example
I've had to scrample the consumer key and secret for obvious reasons so this code can't be tested directly... but the issue seems to be that the parameters that I send over don't match the hash that I'm sending.
The response I receive back from Etrade is: oauth_problem=signature_invalid. I've seen a number of libraries, I'm open to attempting one of those but none seem to match up with Etrade's approach. All thoughts welcome! Thanks in advance.
<!--- Generate epoctime --->
<cfset TheDate = now()>
<cfset TheDate = DateAdd("h",5,TheDate)>
<cfset EpocTime = DateDiff("s", "January 1 1970 00:00", DateConvert("local2utc", TheDate))>
<!--- Set Keys / URL --->
<cfset consumer_key = "aaaaaaaaa">
<cfset consumer_secret = "1111111">
<cfset GetTokenURL = "https://etws.etrade.com/oauth/request_token">
<!--- generate message for oauth signature --->
<cfset message = "GET">
<cfset message = message & "&" & "https%3A%2F%2Fetws.etrade.com%2Foauth%2Frequest_token">
<cfset message = message & "&" & "oauth_callback%3Doob">
<cfset message = message & "%26oauth_consumer_key%3D#consumer_key#">
<cfset message = message & "%26oauth_nonce%3DLTg2ODUzOTQ5MTEzMTY3MzQwMzE%3D">
<cfset message = message & "%26oauth_signature_method%3DHMAC-SHA1">
<cfset message = message & "%26oauth_timestamp%3D#epoctime#">
<!--- debug message --->
<cfoutput>#message#</cfoutput><BR><BR>
<!--- Apply HMAC function to generate signature (absent of token) --->
<cfset oauth_signature = hmac(message, consumer_secret & "&", "HMACSHA1")>
<cfoutput>#oauth_signature#</cfoutput><BR><BR>
<!--- Encode and debug output --->
<cfset oauth_signature = urlencodedformat(toBase64(Binarydecode(oauth_signature , "hex")))>
<cfoutput>#oauth_signature#</cfoutput><BR><BR>
<!--- Make the CFHTTP Request --->
<cfhttp result="result" method="GET" charset="utf-8" url="https://etws.etrade.com/oauth/request_token">
<cfhttpparam name="Authorization" type="header" value="OAuth" />
<cfhttpparam name="realm" type="url" value="">
<cfhttpparam name="oauth_callback" type="url" value="oob">
<cfhttpparam name="oauth_consumer_key" type="url" value=#consumer_key#>
<cfhttpparam name="oauth_nonce" type="url" value="LTg2ODUzOTQ5MTEzMTY3MzQwMzE%3D">
<cfhttpparam name="oauth_signature" type="url" value=#oauth_signature#>
<cfhttpparam name="oauth_signature_method" type="url" value="HMAC-SHA1">
<cfhttpparam name="oauth_timestamp" type="url" value="#epoctime#">
</cfhttp>
<!--- Output the results --->
<cfoutput>#result.FileContent#</cfoutput>
<BR><BR>
<cfdump var="#result#">
Related
I'm constantly doing login POSTs to various APIs using Coldfusion 2016. No matter how specific the API documentation seems to be (with CURL and HTTPie examples) it always seems to be a trial and error process to work out how to pass the required variable using CFHTTP.
Are there any shortcuts I can use to know which CFHTTP method to use first time? OS, age of API, HEAD requests, etc.
Eg. A login designated as OAuth2.0 can require either...
<!--- "form" variables posted as a URL encoded string in the body --->
<cfhttp method="POST" url="https://some.important.api/oauth/oauth20/token">
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded" />
<cfhttpparam type="body" value="grant_type=client_credentials&client_id=#ID#&client_secret=#secret#" />
</cfhttp>
<!--- "form" variables posted as JSON in the body --->
<cfhttp method="POST" url="https://some.important.api/oauth/oauth20/token">
<cfhttpparam type="header" name="Content-Type" value="application/json" />
<cfhttpparam type="body"
value="{ grant_type : 'client_credentials', client_id : '#ID#', client_secret : '#secret#' }" />
</cfhttp>
<!--- Actual form variables posted as such. This is what makes sense from CURL examples but is only correct about 1/3 of the time --->
<cfhttp method="POST" url="https://some.important.api/oauth/oauth20/token">
<cfhttpparam type="formfield" name="grant_type" value="client_credentials" />
<cfhttpparam type="formfield" name="client_id" value="#ID#" />
<cfhttpparam type="formfield" name="client_secret" value="#secret#" />
</cfhttp>
Another example is passing a file using CFHTTP
<!--- Post a file using CFHTTP. Works 1/2 the time --->
<cfhttp method="POST" url="https://some.important.api/postOrder">
<cfhttpparam type="file" name="order_file" file="#pathToCSVfile#" />
<cfhttpparam type="formfield" name="apikey" value="#apiKey#" />
</cfhttp>
<!--- Requires completely manual method the other 1/2 of the time --->
<cfhttp method="POST" url="https://some.important.api/postOrder">
<cfhttpparam type="header" name="content-type" value="multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" />
<cfhttpparam type="body" value='------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="order_file"; filename="myCSVFile"
Content-Type: text/csv
#actualCSVText#
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="apikey"
#apiKey#
------WebKitFormBoundary7MA4YWxkTrZu0gW--' />
</cfhttp>
I am using MonkehTweet Coldfusion wrapper for Twitter Authentication. I have everything up working, but i cannot get my head around posting multiple images using the PostUpdateWithMedia function. I am relatively new to coldfusion, and learning it along the way. A simple call to PostUpdateWithMedia(status="", media="") would post to Twitter with an image, but how can i use this to post multiple images. The PostUpdateWithMedia function from MonkehTweet is,
<cffunction name="postUpdateWithMedia" access="public" output="false" hint="Updates the authenticating user's status. Request must be a POST. A status update with text identical to the authenticating user's current status will be ignored to prevent duplicates.">
<cfargument name="status" required="true" type="String" hint="The text of your status update. URL encode as necessary. Statuses over 140 characters will be forceably truncated." />
<cfargument name="media" required="true" type="string" hint="Up to max_media_per_upload files may be specified in the request, each named media[]. Supported image formats are PNG, JPG and GIF. Animated GIFs are not supported." />
<cfargument name="possibly_sensitive" required="false" type="boolean" default="false" hint="Set to true for content which may not be suitable for every audience." />
<cfargument name="in_reply_to_status_id" required="false" type="String" hint="The ID of an existing status that the update is in reply to." />
<cfargument name="lat" required="false" type="String" hint="The location's latitude that this tweet refers to." />
<cfargument name="long" required="false" type="String" hint="The location's longitude that this tweet refers to." />
<cfargument name="place_id" required="false" type="String" hint="A place in the world. These IDs can be retrieved from geo/reverse_geocode." />
<cfargument name="display_coordinates" required="false" type="String" hint="Whether or not to put a pin on the exact coordinates a tweet has been sent from." />
<cfargument name="checkHeader" required="false" type="boolean" default="false" hint="If set to true, I will abort the request and return the response headers for debugging." />
<cfargument name="timeout" required="false" type="string" default="#variables.instance.timeout#" hint="An optional timeout value, in seconds, that is the maximum time the cfhttp requests can take. If the time-out passes without a response, ColdFusion considers the request to have failed." />
<cfset var strTwitterMethod = '' />
<cfset arguments["media[]"] = arguments.media />
<cfset structDelete(arguments,'media') />
<cfset strTwitterMethod = getCorrectEndpoint('api') & 'statuses/update_with_media.json' />
<cfreturn genericAuthenticationMethod(timeout=getTimeout(), httpURL=strTwitterMethod,httpMethod='POST', parameters=arguments, checkHeader=arguments.checkHeader) />
</cffunction>
I have tried passing in multiple files as,
PostUpdateWithMedia(status="", media="", media=""); but it didnot work. But, I am passing in the multiple media arguments wrong. Can someone help me with how to pass in multiple media arguments.
Unfortunately, MonkehTweet does not include support for the Twitter API media/upload endpoint, which is required for multi-image Tweets - it only supports the deprecated statuses/update_with_media, which still works, but only supports a single image. I also noticed that the code still refers to 140 character Tweets, so it probably has not been updated for some time. I'm not aware of an alternative ColdFusion wrapper for the API, either, so this is something you would need to build for yourself.
I just set up a short toy learning ColdFusion page on a server. The page makes a call out to a cffunction, which gets the current date, determines the year, and then returns a boolean reflecting whether or not the current year is a leap year. This information is displayed on the main page in plain text.
page.cfm
<html>
<head>
<cfinclude template="./func.cfm" />
</head>
<body>
<cfset yearList = "2000;2001,2002/2003,2004,2005;2006/2007,2008,2009;2010,2011,2012" >
<cfloop index="year" list=#yearList# delimiters=",;/" >
<cfset isLeapYear = #My_IsLeapYear(year)# >
<cfif isLeapYear is True>
<cfoutput>
#year# is a leap year!
</cfoutput>
<cfelse>
<cfoutput>
#year# is not a leap year.
</cfoutput>
</cfif>
<br>
</cfloop>
</body>
</html>
func.cfm
<cffunction name="My_IsLeapYear" output="false" access="public" returnType="boolean">
<cfargument name="year" type="numeric" required="true" default="" />
<cfset var isLeapYear = (DaysInYear(CreateDate(arguments.year,1,1)) EQ 366) />
<cfreturn isLeapYear>
</cffunction>
Trying to access this page caused a horrific memory leak and took down the server where it was hosted. I'm at a loss. Any thoughts?
The memory leak was caused to a strange external issue. Thanks for the comments and such.
I think this is a faster way to check this:
<cfset yearList = "2000;2001,2002/2003,2004,2005;2006/2007,2008,2009;2010,2011,2012" >
<cfoutput>
<cfloop index="year" list="#yearList#" delimiters=",;/" >
<p>#year# is <cfif !(val(year) MOD 4 EQ 0)>not </cfif>a leap year!</p>
</cfloop>
</cfoutput>
Avoids the more expensive calls to DaysInYear(). You just need to check if the value of year is a number divisible by 4.
Update
Point taken re: calculation. As for the original function, You can just return the result of the comparison. No need to create a function local variable.
<cffunction name="My_IsLeapYear" output="false" access="public" returnType="boolean">
<cfargument name="year" type="numeric" required="true" default="" />
<cfreturn (DaysInYear(CreateDate(arguments.year,1,1)) EQ 366) />
</cffunction>
and in page.cfm, you can change this code:
<cfset isLeapYear = #My_IsLeapYear(year)# >
<cfif isLeapYear is True>
to a simpler call:
<cfif My_IsLeapYear(year)>
since that function will only return a Boolean value.
I'm trying to insert some data into my merchant account data feed using the Content API. Using the code below, I've been given a "POST requests require a Content-length header. That’s all we know." error, but I've set the content length, so I'm really confused.
One theory I have is that it doesn't know where to put these requests, under my merchant account ID, I have two data feeds, and I'm not sure how to set it to enter into a given data feed.
The XML is stolen from here: https://developers.google.com/shopping-content/batch-operations
#feed = '<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"
xmlns:g="http://base.google.com/ns/1.0"
xmlns:batch="http://schemas.google.com/gdata/batch">
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:sc="http://schemas.google.com/structuredcontent/2009"
xmlns:scp="http://schemas.google.com/structuredcontent/2009/products"
xmlns:app="http://www.w3.org/2007/app">
<batch:operation type="INSERT"/>
<title>Switch 1</title>
<content type="text">A new item available...</content>
<link rel="alternate" type="text/html" href="http://www.example.com/sku123"/>
<sc:id>SKU123</sc:id>
<sc:content_language>en</sc:content_language>
<sc:target_country>US</sc:target_country>
<scp:google_product_category>Electronics > Networking > Hubs &
Switches</scp:google_product_category>
<scp:condition>new</scp:condition>
<scp:price unit="USD">25</scp:price>
<scp:availability>in stock</scp:availability>
</entry>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:sc="http://schemas.google.com/structuredcontent/2009"
xmlns:scp="http://schemas.google.com/structuredcontent/2009/products"
xmlns:app="http://www.w3.org/2007/app">
<batch:operation type="INSERT"/>
<title>Switch 2</title>
<content type="text">Another item available...</content>
<link rel="alternate" type="text/html" href="http://www.example.com/sku124"/>
<sc:id>SKU124</sc:id>
<sc:content_language>en</sc:content_language>
<sc:target_country>US</sc:target_country>
<scp:google_product_category>Electronics > Networking > Hubs &
Switches</scp:google_product_category>
<scp:condition>new</scp:condition>
<scp:price unit="USD">50</scp:price>
<scp:availability>in stock</scp:availability>
</entry>
</feed>'
http = HTTPClient.new
#output = http.post('https://content.googleapis.com/content/v1/<myMerchantID>/items/products/schema/batch', {},"content"=> #feed, "Content-length"=>#feed.length.to_s, "GData-Version"=> "1", "Authorization" => "AuthSub token=myToken", "Content-Type"=> "application/atom+xml")
Are you Authenticating prior to your post?
https://developers.google.com/shopping-content/getting-started/authentication
First off, my project requires me to make my own module so I can't use the various packages and products out there. Having said that, using the correct token, consumer_key, consumer_secret and token_secret variables, creating the correct Base Signature String and thus OAuth Signature from that string, I have had NO problem grabbing Twitter data via my ColdFusion module. If either of them were off, I wouldn't be able to even get Twitter data.
So knowing that my variables are correct, I am still unable to POST a simple status tweet to my Twitter account via ColdFusion. Each time I try I get the same error: "Code:32, Message:'Could not authenticate you'". Since I know it can't be the variables, it has got to be how I'm sending the POST via ColdFusion, but I am unsure of what I am doing wrong. The following is the code I use to submit the POST:
<cfhttp url="https://api.twitter.com/1.1/statuses/update.json" method="POST" throwonerror="yes" >
<cfhttpparam type="header" name="Authorization" value='OAuth oauth_consumer_key="#oauthStruct.oauth_consumer_key#", oauth_nonce="#oauthStruct.oauth_nonce#", oauth_signature="#oauthStruct.oauth_signature#", oauth_signature_method="#oauthStruct.oauth_signature_method#", oauth_timestamp="#oauthStruct.oauth_timestamp#", oauth_token="#oauthStruct.oauth_token#", oauth_version="#oauthStruct.oauth_version#"'>
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
<cfhttpparam type="formfield" name="status" value="#oauthStruct.status#">
</cfhttp>
Can somebody please look over the above code and help me figure out if I'm missing something or submitting incorrectly this call?
Update: I changed the above code to this:
<cfhttp url="https://api.twitter.com/1.1/statuses/update.json" method="POST" throwonerror="yes" >
<cfhttpparam type="header" name="Authorization" value='OAuth oauth_consumer_key="#oauthStruct.oauth_consumer_key#", oauth_nonce="#oauthStruct.oauth_nonce#", oauth_signature="#oauthStruct.oauth_signature#", oauth_signature_method="#oauthStruct.oauth_signature_method#", oauth_timestamp="#oauthStruct.oauth_timestamp#", oauth_token="#oauthStruct.oauth_token#", oauth_version="#oauthStruct.oauth_version#"'>
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
<cfhttpparam type="header" name="oauth_consumer_key" value="#oauthStruct.oauth_consumer_key#">
<cfhttpparam type="header" name="oauth_nonce" value="#oauthStruct.oauth_nonce#">
<cfhttpparam type="header" name="oauth_signature" value="#oauthStruct.oauth_signature#">
<cfhttpparam type="header" name="oauth_signature_method" value="#oauthStruct.oauth_signature_method#">
<cfhttpparam type="header" name="oauth_timestamp" value="#oauthStruct.oauth_timestamp#">
<cfhttpparam type="header" name="oauth_token" value="#oauthStruct.oauth_token#">
<cfhttpparam type="header" name="oauth_version" value="#oauthStruct.oauth_version#">
<cfhttpparam type="body" value="#signature_string#">
</cfhttp>
It still does not work, same authenticate error. Any other ideas?
I figured it out. So that others can understand how this process goes, with the new API (1.1), this is how you post a status update to your Twitter account:
<cfhttp url="https://api.twitter.com/1.1/statuses/update.json" method="POST" throwonerror="yes" >
<cfhttpparam type="header" name="Authorization" value='OAuth oauth_consumer_key="#oauthStruct.oauth_consumer_key#", oauth_nonce="#oauthStruct.oauth_nonce#", oauth_signature="#oauthStruct.oauth_signature#", oauth_signature_method="#oauthStruct.oauth_signature_method#", oauth_timestamp="#oauthStruct.oauth_timestamp#", oauth_token="#oauthStruct.oauth_token#", oauth_version="#oauthStruct.oauth_version#"'>
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
<cfhttpparam type="body" value="status=#oauthStruct.status#">
</cfhttp>
If I did not have the Authorization header parameter, I ended up with a 400 Bad Request error. You put all the necessary OAuth parameters, including your digital signature, in a comma delimited list as shown above, indicate your content-type, and most importantly, you provide as a URL Query String the remaining variables in the body parameter of your cfhttp call.