Why do I get a 401 error when using Ruby to execute a Google Fusion Tables API command? - ruby

I'm using the Ruby gem 'google-api-client', and I make sure to add in all my credentials, but for some reason, I keep getting a 401 error from Google.
Here's my code:
['google/api_client','json'].each{|g| require g}
client_secrets = File.open('client_secrets.json','r').read # My client secrets are stored in this .JSON file
client_secrets_hsh = JSON.parse(client_secrets)
client = Google::APIClient.new
client.authorization.client_id = client_secrets_hsh['installed']['client_id']
client.authorization.client_secret = client_secrets_hsh['installed']['client_secret']
client.authorization.redirect_uri = client_secrets_hsh['installed']['redirect_uris'][0]
client.authorization.access_token = 'MY_ACCESS_TOKEN'
client.authorization.username = 'MY_USER_NAME#gmail.com'
client.authorization.password = 'MY_GOOGLE_ACCOUNT_PASSWORD'
client.authorization.scope = 'https://www.googleapis.com/auth/fusiontables'
request_body = ''
headers = []
headers << ['Content-Type','application/json']
api = client.discovered_api('fusiontables','v1')
result = client.execute(
:api_method => api.to_h['fusiontables.table.list'],
:parameters => {'maxResults' => 1000},
:merged_body => request_body,
:headers => headers
)
puts result.response.body
And here's the response I get back from the puts results.response.body line:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}

Username and password should not be supplied. Try dropping those to start. You only need the access token in order to make API calls. Which, incidentally, I'm not sure where you got that from. Usually you'd need to make a call to fetch_access_token! somewhere. It's normally not something you set via an accessor. There are advanced cases where you would – but you're probably not one of them.

Related

Youtube Data API with Kotlin: The request is missing a valid API key

So I'm trying to use the Youtube Data API with Kotlin + Spring Boot and I've been struggling a bit.
For now, I'm using hardcoded values for the api_key and the access_token for test purposes.
I'm trying to send a request to list my playlists but I keep getting this error:
"error": {
"code": 403,
"message": "The request is missing a valid API key.",
"errors": [
{
"message": "The request is missing a valid API key.",
"domain": "global",
"reason": "forbidden"
}
],
"status": "PERMISSION_DENIED"
Here's my code:
Controller:
#RestController
class PlaylistController(
private val youtubeService: YoutubeService
) {
#GetMapping("/playlists")
fun getPlaylists() : YoutubePlaylistResponse {
return youtubeService.getPlaylists()
}
}
Service:
#Service
class YoutubeService(
private val youtubeClient: YoutubeClient
) {
fun getPlaylists() = youtubeClient.getPlaylists(
access_token = "[ACCESS_TOKEN]",
api_key = "[API_KEY]"
)
}
Client
#FeignClient(name = "youtube", url = "https://www.googleapis.com/youtube/v3")
interface YoutubeClient {
#GetMapping("/playlists")
fun getPlaylists(
#RequestHeader("Authorization", required = true) access_token: String,
#RequestParam("api_key") api_key: String,
#RequestParam("part") part: String = "snippet",
#RequestParam("mine") mine: Boolean = true
): YoutubePlaylistResponse
}
Any thoughts on what I'm doing wrong?
PS: I'm getting the acess_token through the OAuth 2.0 Playground
Edit:
I was calling api_key but it's actually only key.
But now I'm getting a new problem:
"error": {
"code": 401,
"message": "The request uses the \u003ccode\u003emine\u003c/code\u003e parameter but is not properly authorized.",
"errors": [
{
"message": "The r... (464 bytes)]] with root cause
Apparently, it's because I'm trying to access my playlists and it says that I don't have the permission, but when I do the same request using cURL I get an appropriate response. Any thoughts on this?
According to the API documentation, the parameter should be called key rather than api_key:
Every request must either specify an API key (with the key parameter) or provide an OAuth 2.0 token. Your API key is available in the Developer Console's API Access pane for your project.
Source: https://developers.google.com/youtube/v3/docs

Sending a patch request to edit video using vimeo api not working

I am trying to send a patch request to edit a video using vimeo api using ruby. The request is successful but vimeo is not able to read my the payload(the title and description), that is, the the title and the description is not getting changed. I have used HTTParty, RestClient as well as Net::HTTP but none of it works. Below are my code that i have implemented to send a patch request,
RestClient
payload = {"description" => "Test Description", "name" => "Test Video"}
res = RestClient.patch(
vimeo_edit_url,
payload.to_s,
{ "Authorization" => auth })
NET::HTTP
options = {'description' => "Test Description", 'name' => "Test Video"}
edit_req = Net::HTTP::Patch.new(vimeo_edit_url, initheader = { "Authorization" => auth})
edit_req.data = options.to_s
edit_uri = URI(vimeo_edit_url)
edit_http = Net::HTTP.new(edit_uri.host, edit_uri.port)
edit_http.use_ssl = true
edit_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
edit_response = edit_http.request(edit_req)
Usually the response will be a 200 OK with the updated video details but I get a 200 OK with video details(title and description not being changed). It is as if like vimeo is not able to read my payload.
You probably want to be passing payload, not payload.to_s.
That .to_s is turning your nicely arranged hash into a weird rubyified string, and all of the HTTP libraries you mentioned will accept a hash and handle the conversion for you.
Here's what some different representations look like in an irb session:
>> payload = {"description" => "Test Description", "name" => "Test Video"}
>> payload
=> {"description"=>"Test Description", "name"=>"Test Video"}
>> payload.to_s
=> "{"description"=>"Test Description", "name"=>"Test Video"}"
>> require 'json'
=> true
>> payload.to_json
=> "{"description":"Test Description","name":"Test Video"}"

Saving Point to a Google Fitness API (fitness.body.write)

Im trying to save a Point with float value into fitness.body.
Getting value is not a problem, while saving a new point causes 403. No permission to modify data for this source.
Im using DataSetId derived:com.google.weight:com.google.android.gms:merge_weight to find point and read value, and raw:com.google.weight:com.google.android.apps.fitness:user_input to insert data.
.
Here is a workflow using Ruby and google-api-ruby-client:
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/installed_app'
require 'pry'
# Initialize the client.
client = Google::APIClient.new(
:application_name => 'Example Ruby application',
:application_version => '1.0.0'
)
fitness = client.discovered_api('fitness')
# Load client secrets from your client_secrets.json.
client_secrets = Google::APIClient::ClientSecrets.load
flow = Google::APIClient::InstalledAppFlow.new(
:client_id => client_secrets.client_id,
:client_secret => client_secrets.client_secret,
:scope => ['https://www.googleapis.com/auth/fitness.body.write',
'https://www.googleapis.com/auth/fitness.activity.write',
'https://www.googleapis.com/auth/fitness.location.write']
)
client.authorization = flow.authorize
Forming my new data Point:
dataSourceId = 'raw:com.google.weight:com.google.android.apps.fitness:user_input'
startTime = (Time.now-1).to_i # 1 Second ago
endTime = (Time.now).to_i
metadata = {
dataSourceId: dataSourceId,
maxEndTimeNs: "#{startTime}000000000", # Faking nanoseconds with tailing zeros
minStartTimeNs: "#{endTime}000000000",
point: [
{
endTimeNanos: "#{endTime}000000000",
startTimeNanos: "#{startTime}000000000",
value: [
{ fpVal: 80 }
]
}
]
}
Attempting to save the point:
result = client.execute(
:api_method => fitness.users.data_sources.datasets.patch,
:body_object => metadata,
:parameters => {
'userId' => "me",
'dataSourceId' => dataSourceId,
'datasetId' => "#{Time.now.to_i-1}000000000-#{(Time.now).to_i}000000000"
}
)
And as I indicated previously im getting 403. No permission to modify data for this source
#<Google::APIClient::Schema::Fitness::V1::Dataset:0x3fe78c258f60 DATA:{"error"=>{"er
rors"=>[{"domain"=>"global", "reason"=>"forbidden", "message"=>"No permission to modif
y data for this source."}], "code"=>403, "message"=>"No permission to modify data for
this source."}}>
I believe, I selected all required permissions in the scope. I tried submitting the point to both accessible datasetid's for fitness.body.
Please let me know if im doing anything wrong here.
Thank you!
I encountered the same situation, turns out you can NOT insert data points directly into the datasource "raw:com.google.weight:com.google.android.apps.fitness:user_input". From the name, one might guess out this datasource is reserved. So the workaround is to add your own datasource, note should with dataType.name="com.google.weight", like this:
{
"dataStreamName": "xxxx.body.weight",
"dataType": {
"field": [
{
"name": "weight",
"format": "floatPoint"
}
],
"name": "com.google.weight"
},
"dataQualityStandard": [],
"application": {
"version": "1",
"name": "Foo Example App",
"detailsUrl": "http://example.com"
},
"device": {
"model": "xxxmodel",
"version": "1",
"type": "scale",
"uid": "xxx#yyy",
"manufacturer": "xxxxManufacturer"
},
"type": "derived"
}
then after the successful creation, you can use this datasource(datastream id) to insert your own data points, and then the inserted data points will also be included in the datasource "derived:com.google.weight:com.google.android.gms:merge_weight" when you do the querying with suffix "dataPointChanges".
Try adding an Authorization header:
result = client.execute(
:api_method => fitness.users.data_sources.datasets.patch,
:headers => {'Authorization' => 'Bearer YOUR_AUTH_TOKEN'},
:body_object => metadata,
:parameters => {
'userId' => "me",
'dataSourceId' => dataSourceId,
'datasetId' => "#{Time.now.to_i-1}000000000-#{(Time.now).to_i}000000000"
}
)

Can't impersonate on a service-to-service oauth request to google calendar api in ruby

Hi I was late night hacking and testing google api client for Ruby.
I fell in an error perhaps by missunderstanding.
What I already done
I already created an app in google developer console
I enabled the calendar api
I created the Server Key and downloaded the p12.
I managed to see that calendar metadata using the google api explorer and browser oauth, so the ids are confirmed and the permissions too as browser's oauth can view the metadata
What I'm triying to do:
I'm triying to get metadata (I will try events later) from a privately shared calendar where I have read permissions (trough a group) in a google apps for work account in Ruby on a service to service auth
How I'm triying to do it
With this code:
require 'google/api_client'
require 'pp'
client = Google::APIClient.new
cal = client.discovered_api('calendar','v3')
id = 'xxxxxxxxxxxxxxxxxxxxxx#group.calendar.google.com' # id is the confirmed calendar ID
key = Google::APIClient::KeyUtils.load_from_pkcs12('sl.p12', 'notasecret')
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/calendar',
:issuer => 'xxxxxxxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com', # confirmed issuer
:signing_key => key
)
client.authorization.fetch_access_token!
result = client.execute(
:api_method => cal.calendars.get,
:parameters => { 'calendarId' => id }
)
puts result.response.body
pp result
Results colected
When I do this y get a 404, something like "that calendar does not exists"
{
"error": {
"errors": [
{
"domain": "global",
"reason": "notFound",
"message": "Not Found"
}
],
"code": 404,
"message": "Not Found"
}
}
But if i change id per 'primary'
I get:
{
"kind": "calendar#calendar",
"etag": "\"dAAhx6wYoPw2vqRAe54lk5wa0XQ/WEglF6_c5pVHKyggcENvvX1cS9g\"",
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com", #same as issuer id ??? WTF
"summary": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com",
"timeZone": "UTC"
}
Which seems to be a calendar but it's like the default calendar for the "email issuer" account that appears in the server key "email" field
I also tried to ad a :person = 'email' field to client.authorization but then I get an error creating the auth token
I couldn't find a way to access the api as other mail account dirrerent from that #developer.gserviceaccount.com, so what I'm doing wrong?
You can either share the calendar with the service account's email or follow these steps to allow the service account to impersonate any user on that domain, in which case you have to pass the :person => 'email' parameter.

Api errors customization for Rails 3 like Github api v3

I am adding an API on a Rails3 app and its pretty going good.
But I saw the following Github api v3 at http://developer.github.com/v3/
HTTP/1.1 422 Unprocessable Entity
Content-Length: 149
{
"message": "Validation Failed",
"errors": [
{
"resource": "Issue",
"field": "title",
"code": "missing_field"
}
]
}
I liked the error messages structure. But couldn't get it to reproduce.
How can I make my apis to make the response like it?
You could quite easily achieve that error format by adding an ActionController::Responder for your JSON format. See http://api.rubyonrails.org/classes/ActionController/Responder.html for the (extremely vague) documentation on this class, but in a nutshell, you need to override the to_json method.
In the example below I'm calling a private method in an ActionController:Responder which will construct the json response, including the customised error response of your choice; all you have to do is fill in the gaps, really:
def to_json
json, status = response_data
render :json => json, :status => status
end
def response_data
status = options[:status] || 200
message = options[:notice] || ''
data = options[:data] || []
if data.blank? && !resource.blank?
if has_errors?
# Do whatever you need to your response to make this happen.
# You'll generally just want to munge resource.errors here into the format you want.
else
# Do something here for other types of responses.
end
end
hash_for_json = { :data => data, :message => message }
[hash_for_json, status]
end

Resources