I'm trying to follow these Ruby quickstart code(https://developers.google.com/sheets/api/quickstart/ruby#step_2_set_up_the_sample),
require "google/apis/sheets_v4"
require "googleauth"
require "googleauth/stores/file_token_store"
require "fileutils"
OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
APPLICATION_NAME = "Google Sheets API Ruby Quickstart".freeze
CREDENTIALS_PATH = "credentials.json".freeze
# The file token.yaml stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
TOKEN_PATH = "token.yaml".freeze
SCOPE = Google::Apis::SheetsV4::AUTH_SPREADSHEETS_READONLY
##
# Ensure valid credentials, either by restoring from the saved credentials
# files or intitiating an OAuth2 authorization. If authorization is required,
# the user's default browser will be launched to approve the request.
#
# #return [Google::Auth::UserRefreshCredentials] OAuth2 credentials
def authorize
client_id = Google::Auth::ClientId.from_file CREDENTIALS_PATH
token_store = Google::Auth::Stores::FileTokenStore.new file: TOKEN_PATH
authorizer = Google::Auth::UserAuthorizer.new client_id, SCOPE, token_store
user_id = "default"
credentials = authorizer.get_credentials user_id
if credentials.nil?
url = authorizer.get_authorization_url base_url: OOB_URI
puts "Open the following URL in the browser and enter the " \
"resulting code after authorization:\n" + url
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI
)
end
credentials
end
# Initialize the API
service = Google::Apis::SheetsV4::SheetsService.new
service.client_options.application_name = APPLICATION_NAME
service.authorization = authorize
# Prints the names and majors of students in a sample spreadsheet:
# https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
spreadsheet_id = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
range = "Class Data!A2:E"
response = service.get_spreadsheet_values spreadsheet_id, range
puts "Name, Major:"
puts "No data found." if response.values.empty?
response.values.each do |row|
# Print columns A and E, which correspond to indices 0 and 4.
puts "#{row[0]}, #{row[4]}"
end
but I'm getting this error:
Traceback (most recent call last):
5: from quickstart.rb:42:in `<main>'
4: from quickstart.rb:22:in `authorize'
3: from /home/vagrant/.rvm/gems/ruby-2.7.2/gems/googleauth-1.1.2/lib/googleauth/client_id.rb:67:in `from_file'
2: from /home/vagrant/.rvm/gems/ruby-2.7.2/gems/googleauth-1.1.2/lib/googleauth/client_id.rb:67:in `open'
1: from /home/vagrant/.rvm/gems/ruby-2.7.2/gems/googleauth-1.1.2/lib/googleauth/client_id.rb:70:in `block in from_file'
/home/vagrant/.rvm/gems/ruby-2.7.2/gems/googleauth-1.1.2/lib/googleauth/client_id.rb:84:in `from_hash': Expected top level property 'installed' or 'web' to be present. (RuntimeError)
I think it's a authentication issue.
I'm using a service accounts authentication https://developers.google.com/workspace/guides/create-credentials#create_credentials_for_a_service_account
Maybe the code doesn't work for service account authentication, but I couldn't find any documentation that talks about how to integrate Google Sheets with my code using service account authentication.
Okay, I'm gonna say that your credentials configuration did not setup properly.
I'm gonna follow these steps if I were you:
Step 1: Install this gem
https://github.com/googleapis/google-auth-library-ruby
Step 2: Get your Google Service Account Credentials, when this step is done, you should get a .json file. I'm gonna call it google_service_account_credentials.json file.
Step 3: Try this snippet:
require "google/apis/sheets_v4"
require "googleauth"
CREDENTIALS_PATH = "google_service_account_credentials_path/google_service_account_credentials.json"
SCOPE = Google::Apis::SheetsV4::AUTH_SPREADSHEETS_READONLY
# Author & Authen with Google by Google Service Account Credentials
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open(CREDENTIALS_PATH),
scope: SCOPE)
authorizer.fetch_access_token!
# Initialize the API
service = Google::Apis::SheetsV4::SheetsService.new
service.authorization = authorize
# Some demo code to prove it run
# Prints the names and majors of students in a sample spreadsheet:
# https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
spreadsheet_id = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
range = "Class Data!A2:E"
response = service.get_spreadsheet_values spreadsheet_id, range
puts "Name, Major:"
puts "No data found." if response.values.empty?
response.values.each do |row|
# Print columns A and E, which correspond to indices 0 and 4.
puts "#{row[0]}, #{row[4]}"
end
Step 4: Enjoy (hope it helps) <3
Further information you can get from here, I just customize the answer so it suit your need :"))
https://stackoverflow.com/a/56747012/6542152
The code you are using from the Ruby quickstart Is designed to for use with a Authorization credentials for a desktop application
This code can be used with either an installed client or a web client created on google cloud console. Which is what the error message is telling you
Expected top level property 'installed' or 'web' to be present.
The code for use with a service account is not the same
Links: Oauth service
Related
I keep getting Authorization errors when sending a POST request from my React/Ruby app. I'm getting different errors depending on what OOB_URI I'm using.
If I use
OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze then I get the following error:
When I use OOB_URI = "http://127.0.0.1".freeze then I get the following error:
Here's my Ruby code
require "google/apis/sheets_v4"
require "googleauth"
require "googleauth/stores/file_token_store"
require "fileutils"
# Get authorization
OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
# OOB_URI = "http://127.0.0.1".freeze
APPLICATION_NAME = "mimirgettingmarried".freeze
CREDENTIALS_PATH = "/Users/michelleroos/Desktop/mimirgettingmarried/mimirgettingmarried/config/credentials.json".freeze
# The file token.yaml stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
TOKEN_PATH = "token.yaml".freeze
SCOPE = Google::Apis::SheetsV4::AUTH_SPREADSHEETS
# Ensure valid credentials, either by restoring from the saved credentials
# files or intitiating an OAuth2 authorization. If authorization is required,
# the user's default browser will be launched to approve the request.
# #return [Google::Auth::UserRefreshCredentials] OAuth2 credentials
def authorize
client_id = Google::Auth::ClientId.from_file CREDENTIALS_PATH
token_store = Google::Auth::Stores::FileTokenStore.new file: TOKEN_PATH
authorizer = Google::Auth::UserAuthorizer.new client_id, SCOPE, token_store
user_id = "default"
credentials = authorizer.get_credentials user_id
if credentials.nil?
url = authorizer.get_authorization_url base_url: OOB_URI
puts "Open the following URL in the browser and enter the " \
"resulting code after authorization:\n" + url
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI
)
end
credentials
end
# Initialize the API
service = Google::Apis::SheetsV4::SheetsService.new
service.client_options.application_name = APPLICATION_NAME
service.authorization = authorize
spreadsheet_id = '17bXAJELWjXIRmcJ-RPeg5_W4wszqZI3QxvE_ZIL3L6A'
range = 'rsvp'
class Api::RsvpsController < ApplicationController
def create # append
values = [
'hi', 'hello'
]
# TODO: Assign values to desired members of `request_body`:
request_body = Google::Apis::SheetsV4::ValueRange.new(values: values)
response = service.append_spreadsheet_value(spreadsheet_id, range, request_body)
# TODO: Change code below to process the `response` object:
puts response.to_json
end
def show
# range = "Class Data!A2:E"
# response = service.get_spreadsheet_values(spreadsheet_id, range)
# puts "Name, Major:"
# puts "No data found." if response.values.empty?
# response.values.each do |row|
# # Print columns A and E, which correspond to indices 0 and 4.
# puts "#{row[0]}, #{row[4]}"
# end
range_names = [
# Range names ...
]
result = service.batch_get_spreadsheet_values(spreadsheet_id)
# result = service.batch_get_spreadsheet_values(spreadsheet_id, ranges: range_names)
puts "#{result.value_ranges.length} ranges retrieved."
end
end
Here's how I set up my credentials in Google Cloud Console:
I have followed all the steps to set up a Domain-Wide Delegation of Authority (https://developers.google.com/admin-sdk/reports/v1/guides/delegation) and followed this link too (https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority) and I ended up creating this class in Ruby.
class Gsuite::ServiceAccount
def initialize(person: nil)
end
def authorized_client
Google::Auth::ServiceAccountCredentials.make_creds(
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(File.read AppConfig[:gsuite_service_account]
[:credentials_file]),
scope: AppConfig[:gsuite_service_account][:scope])
client = authorizer.fetch_access_token!
end
end
This class returns me this hash
{"access_token"=>"a_long_token_string_here", "expires_in"=>3600, "token_type"=>"Bearer"}
Then, I've created this method (within my Admin class) to connect to Gmail Service
def gsuite_client_access
#client ||= Gsuite::ServiceAccount.new(person: self.email.to_s).authorized_client
authorized_client = Google::Apis::GmailV1::GmailService.new
authorized_client.authorization = #client
authorized_client
end
So, when I try to list my Gmail Messages with this line in another part of the code
inbox = current_admin.gsuite_client_access.list_user_messages('me', max_results: 10)
I get the following error message =>
Sending HTTP get https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=10
401
#<Hurley::Response GET https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=10 == 401 (238 bytes) 645ms>
Caught error Unauthorized
Error - #<Google::Apis::AuthorizationError: Unauthorized>
Retrying after authentication failure
Google::Apis::AuthorizationError: Unauthorized
Any ideas what's missing here?
Finally, I got it working. Turns out, you need to use this line to use the sub method to the "impersonated user" to be able to connect.
authorizer.sub = #person
And, for your delight, here is the updated test code for reading Gmail messages so you can follow in case you want to use it. Just remember to save the credentials.json file in your project folder to make it work and use the same scope you added in the GSuite Dashboard.
class Gsuite::ServiceAccount
def initialize(person: nil)
#person = person
end
def read_messages
client = service_account_access
inbox = client.list_user_messages(#person, max_results: 5, label_ids: "INBOX" )
if inbox.result_size_estimate.nonzero?
inbox.messages.each do |message|
response = client.get_user_message(#person, message.id)
end
end
end
private
def service_account_access
token = authorized_client
client = Signet::OAuth2::Client.new(access_token: token['access_token'])
client.expires_in = Time.current + token["expires_in"]
auth_client = Google::Apis::GmailV1::GmailService.new
auth_client.authorization = client
auth_client
end
def authorized_client
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(File.read AppConfig[:credentials_file]),
scope: AppConfig[:gsuite_scope]).dup
authorizer.sub = #person
authorizer.fetch_access_token!
end
end
How do I authorize a service account for Google Calendar API in Ruby? I tried the quick start guide, but it crashed.
https://developers.google.com/calendar/quickstart/ruby#step_3_set_up_the_sample
quickstart.rb
require 'google/apis/calendar_v3'
require 'googleauth'
require 'googleauth/stores/file_token_store'
require 'fileutils'
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'.freeze
APPLICATION_NAME = 'Google Calendar API Ruby Quickstart'.freeze
CLIENT_SECRETS_PATH = 'client_secrets.json'.freeze
CREDENTIALS_PATH = 'token.yaml'.freeze
SCOPE = Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY
##
# Ensure valid credentials, either by restoring from the saved credentials
# files or intitiating an OAuth2 authorization. If authorization is required,
# the user's default browser will be launched to approve the request.
#
# #return [Google::Auth::UserRefreshCredentials] OAuth2 credentials
def authorize
client_id = Google::Auth::ClientId.from_file(CLIENT_SECRETS_PATH) ### LINE 19
token_store = Google::Auth::Stores::FileTokenStore.new(file: CREDENTIALS_PATH)
authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, token_store)
user_id = 'default'
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: OOB_URI)
puts 'Open the following URL in the browser and enter the ' \
'resulting code after authorization:\n' + url
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI
)
end
credentials
end
# Initialize the API
service = Google::Apis::CalendarV3::CalendarService.new
service.client_options.application_name = APPLICATION_NAME
service.authorization = authorize
# Fetch the next 10 events for the user
calendar_id = 'primary'
response = service.list_events(calendar_id,
max_results: 10,
single_events: true,
order_by: 'startTime',
time_min: Time.now.iso8601)
puts 'Upcoming events:'
puts 'No upcoming events found' if response.items.empty?
response.items.each do |event|
start = event.start.date || event.start.date_time
puts "- #{event.summary} (#{start})"
end
Console
>ruby quickstart.rb
C:/ruby23/lib/ruby/gems/2.3.0/gems/googleauth-0.6.2/lib/googleauth/client_id.rb:97:in `from_hash': Expected top level property 'installed' or 'web' to be present. (RuntimeError)
from C:/ruby23/lib/ruby/gems/2.3.0/gems/googleauth-0.6.2/lib/googleauth/client_id.rb:83:in `block in from_file'
from C:/ruby23/lib/ruby/gems/2.3.0/gems/googleauth-0.6.2/lib/googleauth/client_id.rb:80:in `open'
from C:/ruby23/lib/ruby/gems/2.3.0/gems/googleauth-0.6.2/lib/googleauth/client_id.rb:80:in `from_file'
from quickstart.rb:19:in `authorize'
from quickstart.rb:39:in `<main>'
The (outdated) documentation for authorizing service accounts only has examples for Java and Python.
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
Old similar question with 0 answers: Google Calendar API in Ruby using Service Account
For anyone still looking this is what worked for me:
require 'google/apis/calendar_v3'
require 'googleauth'
scope = 'https://www.googleapis.com/auth/calendar'
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open('/path/to/creds.json'),
scope: scope)
authorizer.fetch_access_token!
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = authorizer
calendar_id = 'primary'
response = service.list_events(calendar_id,
max_results: 10,
single_events: true,
order_by: 'startTime',
time_min: Time.now.iso8601)
puts 'Upcoming events:'
puts 'No upcoming events found' if response.items.empty?
response.items.each do |event|
start = event.start.date || event.start.date_time
puts "- #{event.summary} (#{start})"
end
The trick was finding the docs for the google-auth-library-ruby
https://github.com/googleapis/google-auth-library-ruby
OK I found a way.
https://developers.google.com/api-client-library/ruby/auth/service-accounts
require 'google/apis/calendar_v3'
require 'googleauth'
# Get the environment configured authorization
scopes = ['https://www.googleapis.com/auth/calendar']
authorization = Google::Auth.get_application_default(scopes)
# Clone and set the subject
auth_client = authorization.dup
auth_client.sub = 'myemail#mydomain.com'
auth_client.fetch_access_token!
# Initialize the API
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = auth_client
# Fetch the next 10 events for the user
calendar_id = 'primary'
response = service.list_events(calendar_id,
max_results: 10,
single_events: true,
order_by: 'startTime',
time_min: Time.now.iso8601)
puts 'Upcoming events:'
puts 'No upcoming events found' if response.items.empty?
response.items.each do |event|
start = event.start.date || event.start.date_time
puts "- #{event.summary} (#{start})"
end
And
>set GOOGLE_APPLICATION_CREDENTIALS=client_secrets.json
C:\Users\Chloe\workspace>ruby quickstart.rb
Upcoming events:
- Test (2018-05-17)
- SSL Certificate for CMS (2019-02-13)
But I wonder where it saves the refresh token and access token? All I have to do now is make it work for ephemeral file systems like Heroku and store the tokens in the database.
Where is user_id supposed to come from in this example?
Using the command line example from here:
https://github.com/google/google-auth-library-ruby
I've only changed the example by removing the '/path/to/' and there is a client_secrets.json in my working directory.
require 'googleauth'
require 'googleauth/stores/file_token_store'
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'
scope = 'https://www.googleapis.com/auth/drive'
client_id = Google::Auth::ClientId.from_file('client_secrets.json')
token_store = Google::Auth::Stores::FileTokenStore.new(
:file => 'tokens.yaml')
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: OOB_URI )
puts "Open #{url} in your browser and enter the resulting code:"
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI)
end
Running this code prints out a url to plug into my browser. The url takes me to a consent screen. After I accept, I'm given a code to plug back into the terminal.
Then I get the error:
NameError: undefined local variable or method `user_id' for main:Object
... because obviously user_id is not defined, but where do I get it?
Alternately, is there a better, newer, or better documented oauth 2.0 library I should be using for Ruby in combination with all of the youtube apis?
For anyone else that is wondering I believe that you can safely set
user_id='default'
https://developers.google.com/google-apps/calendar/quickstart/ruby
This will present the user with a "choose account" dialog if the user is redirected to the authorizer.get_authorization_url
The user_id should the email address of the person who clicks the link.
I'm getting the following error message when I try to do an OAuth2 connection to google.
.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/google-api-client-0.7.1/lib/google/api_client/auth/file_storage.rb:49:in `at': can't convert nil into an exact number (TypeError)
Looking at the source this is trying to read a cached credentials file and is failing to parse an attribute called issued_at.
I initially set up my app with the wrong port in the google developer console. Now I've updated the client_secrets.json but I'm continually getting this error.
My code is trying doing the calendar example from the google site, but converted to use the admin directory API, but it isn't getting beyond the auth step.
Where is this cached value coming from?
require 'rubygems'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/file_storage'
require 'sinatra'
require 'logger'
enable :sessions
CREDENTIAL_STORE_FILE = "client_secrets.json"
def logger; settings.logger end
def api_client; settings.api_client; end
def directory_api; settings.directory; end
def user_credentials
# Build a per-request oauth credential based on token stored in session
# which allows us to use a shared API client.
#authorization ||= (
auth = api_client.authorization.dup
auth.redirect_uri = to('/oauth2callback')
auth.update_token!(session)
auth
)
end
configure do
log_file = File.open('directory.log', 'a+')
log_file.sync = true
logger = Logger.new(log_file)
logger.level = Logger::DEBUG
client = Google::APIClient.new(
:application_name => 'Ruby Directory sample',
:application_version => '1.0.0')
puts "store file : #{CREDENTIAL_STORE_FILE}"
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
if file_storage.authorization.nil?
client_secrets = Google::APIClient::ClientSecrets.load
client.authorization = client_secrets.to_authorization
client.authorization.scope = 'https://www.googleapis.com/auth/admin.directory.user'
else
client.authorization = file_storage.authorization
end
# Since we're saving the API definition to the settings, we're only retrieving
# it once (on server start) and saving it between requests.
# If this is still an issue, you could serialize the object and load it on
# subsequent runs.
directory = client.discovered_api('admin', "directory_v1")
set :logger, logger
set :api_client, client
set :directory, directory
end
before do
# Ensure user has authorized the app
unless user_credentials.access_token || request.path_info =~ /\A\/oauth2/
redirect to('/oauth2authorize')
end
end
after do
# Serialize the access/refresh token to the session and credential store.
session[:access_token] = user_credentials.access_token
session[:refresh_token] = user_credentials.refresh_token
session[:expires_in] = user_credentials.expires_in
session[:issued_at] = user_credentials.issued_at
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
file_storage.write_credentials(user_credentials)
end
get '/oauth2authorize' do
# Request authorization
redirect user_credentials.authorization_uri.to_s, 303
end
get '/oauth2callback' do
# Exchange token
user_credentials.code = params[:code] if params[:code]
user_credentials.fetch_access_token!
redirect to('/')
end
get '/' do
result = api_client.execute(:api_method => directory.users.list)
# # Fetch list of events on the user's default calandar
# result = api_client.execute(:api_method => calendar_api.events.list,
# :parameters => {'calendarId' => 'primary'},
# :authorization => user_credentials)
[result.status, {'Content-Type' => 'application/json'}, result.data.to_json]
end
Change the line to CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json" and then rename the json you downloaded from the Google dashboard to client_secrets.json per convention. CREDENTIAL_STORE_FILE is where your OAuth tokens get stored and is created by the FileStorage instance.