I'm implementing the google drive api using the OAuth2 gem (ruby on rails). I'm not using the client library because I'm also integrating other API's, so I'm trying to make all these calls as modular as possible. I'm having trouble with the this request: POST https://www.googleapis.com/drive/v2/changes/watch.
I keep getting this error:
{"errors"=>[{"domain"=>"global", "reason"=>"required", "message"=>"entity.resource"}],
"code"=>400, "message"=>"entity.resource"}: { "error": { "errors": [ { "domain":
"global", "reason": "required", "message": "entity.resource" } ], "code": 400,
"message": "entity.resource" } }
which is not very useful. It may not be Google. It could be OAuth2, but I don't think so, because the debugger gets to the response after making the connection. Well, at this point, I don't know anything, so any help is appreciated. There is THIS GUY who has exactly the same error code as me, as well as the same conclusion.
Anyway, the relevant code parts:
First, the parameters I pass to OAuth2::AccessToken's post method(I need more than 10 rep to post another link, but here is the dochttp://rdoc.info/github/intridea/oauth2/ebe4be038ec14b349682/OAuth2/AccessToken#post-instance_method)(you can click on the request method to see how the params are handled)
base_url = request.protocol + request.host_with_port
channel_id = (0...50).map { ('a'..'z').to_a[rand(26)] }.join
body_post = {:id => channel_id,:type => 'web_hook',:address => base_url + "/googledrive/webhook"}
headers = {'Content-Type' => 'application/json'}
response = makeApiCall(token,"google_drive","/changes/watch","post",{},body_post,headers)
All this does is built my request with my the appropriate request body and headers for the call
Here is the relevant part from makeApiCall (at the line token.post is where the request is made, and where it breaks)
params = {"oauth_consumer_key" => ENV[key], "access_token" => token.token}.merge(params)
#body = Rack::Utils.build_query(body)
opts = {
:params => params,
:body => body,
:headers => headers
}
if(method=="get")
response = token.get(base + path,:opts => opts)
elsif(method=="post")
debugger
response = token.post(base + path,:opts => opts)
end
This is my first or second post, so forgive me if I messed anything up.
Okay, after struggling for so long, I figured out why it wasn't working. I forgot to do this:
JSON.generate(body_post)
So that my body was actually in application/json form.
Also, this line:
response = token.post(base + path,:opts => opts)
should be this:
response = token.post(base + path,opts)
simply because I'm not assigning the opts key, but the variable that I'm passing. Silly me.
Related
I'm trying to set up Apple Push Notifications through a Rails back-end. I'm using the NetHttp2 gem to create the connection and jwt to sign my tokens.
Here's a sample of the responses I get when trying various combinations of post requests. I get a quick response if I omit the "authorization" header, and similar if I give it garbage or a fake token. But if I use the actual token that I've encoded, the connection times out. I have no idea what Apple is doing on the back-end - I get no message at all.
[22] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'hello' => 'hello' }", headers: { "apns-topic" => "something" })
=> #<NetHttp2::Response:0x007fbee8226c90 #body="{\"reason\":\"MissingProviderToken\"}", #headers={":status"=>"403", "apns-id"=>"690A959F-27F0-5B5C-1173-0316D6CE4C91"}>
[23] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'hello' => 'hello' }", headers: { 'authorization' => "bearer garbage", "apns-topic" => "something" })
=> #<NetHttp2::Response:0x007fbeebbbf060 #body="{\"reason\":\"InvalidProviderToken\"}", #headers={":status"=>"403", "apns-id"=>"8EF335C7-1F05-1D99-5425-CCF200960626"}>
[24] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'hello' => 'hello' }", headers: { 'authorization' => "bearer snwekrwunlsdfu.sdlfknweru.awepi234np2", "apns-topic" => "something" })
=> #<NetHttp2::Response:0x007fbee7f38f88 #body="{\"reason\":\"InvalidProviderToken\"}", #headers={":status"=>"403", "apns-id"=>"C3B48281-C7F1-22B0-6159-0FD0B79C1D43"}>
[25] pry(main)> request = client.call(:post, '/3/device/adknerulkdf2345nl',
body: "{ 'aps' => { 'alert' => 'hello' }", headers: { 'authorization' => "bearer #{token}", "apns-topic" => "something" })
SocketError: Socket was remotely closed
How do I debug this? Is there a way to view what is happening on the Apple server?
If not, has anyone set this up and has some tips? Honestly, I am not at all sure I am properly encoding the JWT token. I am attempting to use my p8 token from Apple and am using the 'ES256' settings but I don't know how to validate it. If I make another request with my token + garbage, it also hangs forever.
Here's the rest of the relevant code:
client = NetHttp2::Client.new("https://api.development.push.apple.com:443",
{ connect_timeout: 100000})
token = JWT.encode({'alg': 'ES256', 'kid': APNS_KEY_ID},
(OpenSSL::PKey::EC.new #p8key), 'ES256',
{'iss': TEAM_ID, 'iat': DateTime.now().to_time.to_i})
After much heartache, the solution
I'm still not totally sure what was going on with Apple, but the answer was with my token encoding. The proper format is:
token = JWT.encode(
{
"iss": TEAM_ID,
"iat": DateTime.now().to_time.to_i
},
private_key,
ALGORITHM,
header_fields= {
"alg": ALGORITHM,
"kid": APNS_KEY_ID
}
)
It seems if you give Apple something with the format authorization => bearer WHATEVER it will hang forever trying to authenticate.
The JWT library is very particular about order and keys (as of 6/29/17).
I want to integrate the Routific API into a Rails application. I made my request with HTTParty but I get the following message {"error"=>"Expecting JSON data in the body"}
I don't understand... I am sending the json in the body. Please check my code below.
Thanks for your help
Julien
file = File.read("#{Rails.root}/app/controllers/demo.json")
api_key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ODE3M2ZiNTk1NzRjZTFiMmZhNWEwNTAiLCJpYXQiOjE0Nzc5MTg2NDZ9.extq0nKtPGwj6VXxMpj9u8uapasKVSorWeTBFW7HgOE"
headers = {
"Content-Type:" => "application/json",
'authorization'=> 'bearer ' + api_key
}
#results = HTTParty.post("https://api.routific.com/v1/vrp",
:headers => headers,
:body => file.to_json)
you added invalid headers
it should be "Content-Type" instead of "Content-Type:" (without colon)
I'm building an app in Rails using the Spotify web API. I built a method to refresh a user's token, but am receiving a 400 error. According the the Spotify Web API docs, the header of my request needs to be in the following format:
Authorization: Basic <base64 encoded client_id:client_secret>
Using Httparty gem, here's the POST method to refresh the access token:
def refresh_token
client_id = "foo"
client_secret = "bar"
client_id_and_secret = Base64.encode64("#{client_id}:#{client_secret}")
result = HTTParty.post(
"https://accounts.spotify.com/api/token",
:body => {:grant_type => "refresh_token",
:refresh_token => "#{self.oauth_refresh_token}"},
:headers => {"Authorization" => "Basic #{client_id_and_secret}"}
)
end
Here's what "result" ends up being:
=> #<HTTParty::Response:0x7f92190b2978 parsed_response={"error"=>"invalid_client", "error_description"=>"Invalid client secret"}, #response=#<Net::HTTPBadRequest 400 Bad Request readbody=true>, #headers={"server"=>["nginx"], "date"=>["Sun, 31 Aug 2014 22:28:38 GMT"], "content-type"=>["application/json"], "content-length"=>["70"], "connection"=>["close"]}>
I can decode client_id_and_secret and it returns "foo:bar", so I'm at a loss as to why I'm receiving a 400 error. Any insight is much appreciated.
Found the issue... it was with the Base64 encoding in Ruby. Apparently (as shown in Strange \n in base64 encoded string in Ruby) using the Base64.encode64('') method adds an extra line within the code. Using Base64.strict_encode64('') solved the issue.
Updated code:
def refresh_token
client_id = "foo"
client_secret = "bar"
client_id_and_secret = Base64.strict_encode64("#{client_id}:#{client_secret}")
result = HTTParty.post(
"https://accounts.spotify.com/api/token",
:body => {:grant_type => "refresh_token",
:refresh_token => "#{self.oauth_refresh_token}"},
:headers => {"Authorization" => "Basic #{client_id_and_secret}"}
)
end
I need to send different request with headers and body to PayPal. I want to use standatd class NET::HTTP, so here is my code(ISN'T WORKING):
require "net/http"
require "uri"
header = {...}
body = {...}
url = "https://svcs.sandbox.paypal.com/AdaptiveAccounts/GetVerifiedStatus"
uri = URI.parse(url)
args = { 'header' => header,'body' => body }
res = Net::HTTP.post_form(uri, args)
puts res.status
Gives me error:
C:/Ruby193/lib/ruby/1.9.1/net/protocol.rb:141:in `read_nonblock': An existing connection was forcibly closed by the remote host. (Errno::ECONNRESET)
EDIT
Second variant:
require 'httpclient'
require 'xmlsimple'
header = {"X-PAYPAL-SECURITY-USERID" => "tok261_biz_api.abc.com",
"X-PAYPAL-SECURITY-PASSWORD" => "1244612379",
"X-PAYPAL-SECURITY-SIGNATURE" => "lkfg9groingghb4uw5",
"X-PAYPAL-REQUEST-DATA-FORMAT" => "NV",
"X-PAYPAL-RESPONSE-DATA-FORMAT" => "XML",
"X-PAYPAL-APPLICATION-ID" => "APP-80W284485P519543T"
}
#data to be sent in the request
data = {"emailAddress" => "denmed_1342605975_biz#gmail.com",
"firstName"=> "Den",
"lastName" => "Med",
"matchCriteria"=> "NAME",
"requestEnvelope.errorLanguage" => "en_US"}
#initialize the request
clnt = HTTPClient.new
#API end point(sandbox)
uri = "https://svcs.sandbox.paypal.com/AdaptiveAccounts/GetVerifiedStatus"
#make the post
res = clnt.post(uri, data, header)
if res.status == 200
#xml = XmlSimple.xml_in(res.content)
if #xml['accountType']!=nil
account_type = #xml['accountType'][0]
#its pretty obvious from here init?
if account_type.to_s() == "Business"
puts "Business account!"
elseif account_type.to_s() == "Premier"
puts "Premier Account!"
end
elseif account_type.to_s() == "Personal"
puts "Personal account!"
else
puts "Account type not null but not a valid PayPal account type."
end
else
puts "Gee! sorry! something went seriously wrong"
end
This method - constantly gives me - Account type not null but not a valid PayPal account type.
But it is verified in Sandbox ! Tried to leave blank field, but it gave me the same !
Thanks for help in advance !
I am not familiar with your second code example, however I have used the first before (Net::HTTP) and I find it works quite well. I have generally only used it for GET, but I will try to advise on POST :)
First Thing: Headers should not be set as data to be sent with the request (which it appears you are doing) - instead, each header should be set as an attribute of the request:
request['X-HEADER_NAME'] = header_value
Here is how I suggest your code block should look if using Net::HTTP:
require "net/http"
require "uri"
url = "https://svcs.sandbox.paypal.com/AdaptiveAccounts/GetVerifiedStatus"
uri = URI.parse(url)
req = Net::HTTP::Post.new(url)
req["X-PAYPAL-SECURITY-USERID"] = "tok261_biz_api.abc.com"
req["X-PAYPAL-SECURITY-PASSWORD"] = "1244612379"
req["...."] = "...." .... etc for all headers
req.set_form_data( {"emailAddress" => "denmed_1342605975_biz#gmail.com",
"firstName"=> "Den",
"lastName" => "Med",
"matchCriteria"=> "NAME",
"requestEnvelope.errorLanguage" => "en_US"} )
Net::HTTP.start(uri.host, uri.port) do |http|
response = http.request(req)
return response
end
I suggest giving something like that a try, and if it doesn't work, can you give me the exact wording of the given error?
Note
Another suggestion is that, whenever my application receives Errno::ECONNRESET, it means that the back end server is not reachable (in this case I guess that would be the paypal server) - Are you positive the server is running? And are you positive there are no firewalls or anything in place which are preventing you from connecting?
NOTE: "object" is a placeholder work, as I don't think I should be saying what the controller does specifically.
so, I have multiple ways of calling my apps API, the following works in the command line:
curl -H 'Content-Type: application/xml' -d '<object><name>Test API object</name><password>password</password><description>This is a test object</description></object>' "http://acme.example.dev/objects.xml?api_key=1234"
the above command generates the following request in the devlog:
Processing ObjectsController#create to xml (for 127.0.0.1 at 2011-07-07 09:17:51) [POST]
Parameters: {"format"=>"xml", "action"=>"create", "api_key"=>"1234", "controller"=>"objects",
"object"=>{"name"=>"Test API object", "description"=>"This is a test object", "password"=>"[FILTERED]"}}
Now, I'm trying to write tests for the actions using the API, to make sure the API works, as well as the controllers.
Here is my current (broken) httparty command:
response = post("create", :api_key => SharedTest.user_api_key, :xml => data, :format => "xml")
this command generates the following request in the testlog:
Processing ObjectsController#create to xml (for 0.0.0.0 at 2011-07-07 09:37:35) [POST]
Parameters: {
"xml"=>"<object><name><![CDATA[first post]]></name>
<description><![CDATA[Things are not as they used to be]]></description>
<password><![CDATA[WHEE]]></password>
</object>",
"format"=>"xml",
"api_key"=>"the_hatter_wants_to_have_tea1",
"action"=>"create",
"controller"=>"objects
So, as you can see, the command line command actually generates the object hash from the xml, whereas the httparty command ends up staying in xml, which causes problems for the create method, as it needs a hash.
Any ideas / proper documentation?
Current documentation says that post takes an url, and "options" and then never says what options are available
**EDIT:
as per #Casper's suggestion, my method now looks like this:
def post_through_api_to_url(url, data, api_key = SharedTest.user_api_key)
response = post("create", {
:query => {
:api_key => api_key
},
:headers => {
"Content-Type" => "application/xml"
},
:body => data
})
ap #request.env["REQUEST_URI"]
assert_response :success
return response
end
unfortunately, the assert_response fails, because the authentication via the api key fails.
looking at the very of of the request_uri, the api_key isn't being set properly... it shows:
api_key%5D=the_hatter_wants_to_have_tea1"
but it should just be equals, without the %5D (right square bracket)
I think this is how you're supposed to use it:
options = {
:query => {
:api_key => 1234
},
:headers => {
"Content-Type" => "application/xml"
},
:body => "<xmlcode>goes here</xmlcode>"
}
post("/create", options)
Forgive me for being basic about it but if you only want to send one variable as a parameter, why don't you do as Casper suggests, but just do:
post("/create?api_key=1234", options)
Or rather than testing HTTParty's peculiarities in accessing your API, perhaps write your tests using Rack::Test? Very rough example...
require "rack/test"
require "nokogiri"
class ObjectsTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyApp.new
end
def create_an_object(o)
authorize "x", "1234" # or however you want to authenticate using query params
header 'Accept', 'text/xml'
header 'Content-Type', 'text/xml'
body o.to_xml
post "/create"
xml = Nokogiri::XML(last_response.body)
assert something_logic_about(xml)
end
end