Just as I thought that I "hacked" this ... I realize that I don't understand it anyway.
Important note:
I want to use "server-to-server approved" access so I can avoid Google's crazy approval nightmare for all operations on google drive. My users hate having to approve the script actions on every new copy of my master spreadsheet that we create.
I am trying to use DriveV3 - gem 'google-api-client', '0.9.11'
And I am trying to get authentication working.
I have managed all the initial setup of both console.xxx.xx and admin.google.xxx
So now I have a .p12 file with a Server-to-server approved credentials.
The following code has been used to get a service:
def self.service_client
keypath = Rails.root.join('config','xxxxxxxx.p12').to_s
#client = Google::Apis::DriveV3::DriveService.new
#client.client_options.application_name = "Rails2"
#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/drive",
:issuer => "rails2#autoa-automations.iam.gserviceaccount.com",
:signing_key => Google::APIClient::KeyUtils.load_from_pkcs12(keypath, "yyyy"))
#client.authorization.fetch_access_token!
end
I did this in my before_filter:
session[:google_token] = GoogleDrive::GoogleDocs.service_client
And then use it something like ...
def list_google_docs
gtoken = session[:google_token]
google_session = Google::Apis::DriveV3::DriveService.new
google_session.authorization = gtoken
#google_docs = []
page_token = nil
begin
(myfiles, page_token) = google_session.list_files(page_size: 10, q: "'MYROOTFOLDERID' in parents", fields: 'files(id, mimeType, name), nextPageToken')
#for file in google_session.drive.list_files(page_size: 10, fields: 'nextPageToken, files(id, mime name)')
myfiles.files.each do |file|
filename = file.name
if filename.include?('body')
#google_docs << filename
end
end
end while page_token
end
But I keep getting
dailyLimitExceededUnreg: Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.
It worked initially ...
Anyone who can help?
I simply don't get the hole Google authentication approach ... I have tried a lot but this is too complicated for me ...
Related
I am using omniauth-oauth2 in rails to authenticate to a site which supports oauth2. After doing the oauth dance, the site gives me the following, which I then persist into the database:
Access Token
Expires_AT (ticks)
Refresh token
Is there an omniauth method to refresh the token automatically after it expires or should I write custom code which to do the same?
If custom code is to be written, is a helper the right place to write the logic?
Omniauth doesn't offer this functionality out of the box so i used the previous answer and another SO answer to write the code in my model User.rb
def refresh_token_if_expired
if token_expired?
response = RestClient.post "#{ENV['DOMAIN']}oauth2/token", :grant_type => 'refresh_token', :refresh_token => self.refresh_token, :client_id => ENV['APP_ID'], :client_secret => ENV['APP_SECRET']
refreshhash = JSON.parse(response.body)
token_will_change!
expiresat_will_change!
self.token = refreshhash['access_token']
self.expiresat = DateTime.now + refreshhash["expires_in"].to_i.seconds
self.save
puts 'Saved'
end
end
def token_expired?
expiry = Time.at(self.expiresat)
return true if expiry < Time.now # expired token, so we should quickly return
token_expires_at = expiry
save if changed?
false # token not expired. :D
end
And before making the API call using the access token, you can call the method like this where current_user is the signed in user.
current_user.refresh_token_if_expired
Make sure to install the rest-client gem and add the require directive require 'rest-client' in the model file. The ENV['DOMAIN'], ENV['APP_ID'] and ENV['APP_SECRET'] are environment variables that can be set in config/environments/production.rb (or development)
In fact, the omniauth-oauth2 gem and its dependency, oauth2, both have some refresh logic built in.
See in https://github.com/intridea/oauth2/blob/master/lib/oauth2/access_token.rb#L80
# Refreshes the current Access Token
#
# #return [AccessToken] a new AccessToken
# #note options should be carried over to the new AccessToken
def refresh!(params = {})
fail('A refresh_token is not available') unless refresh_token
params.merge!(:client_id => #client.id,
:client_secret => #client.secret,
:grant_type => 'refresh_token',
:refresh_token => refresh_token)
new_token = #client.get_token(params)
new_token.options = options
new_token.refresh_token = refresh_token unless new_token.refresh_token
new_token
end
And in https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L74 :
self.access_token = access_token.refresh! if access_token.expired?
So you may not be able to do it directly with omniauth-oauth2, but you can certainly do something along the lines of this with oauth2:
client = strategy.client # from your omniauth oauth2 strategy
token = OAuth2::AccessToken.from_hash client, record.to_hash
# or
token = OAuth2::AccessToken.new client, token, {expires_at: 123456789, refresh_token: "123"}
token.refresh!
Eero's answer unlocked a path for me to solve this. I have a helper concern for my classes which get me a GmailService. As part of this process, the user object (which contains the google auth info) gets checked if it's expired. If it has, it refreshes before returning the service.
def gmail_service(user)
mail = Google::Apis::GmailV1::GmailService.new
# Is the users token expired?
if user.google_token_expire.to_datetime.past?
oauth = OmniAuth::Strategies::GoogleOauth2.new(
nil, # App - nil seems to be ok?!
"XXXXXXXXXX.apps.googleusercontent.com", # Client ID
"ABC123456" # Client Secret
)
token = OAuth2::AccessToken.new(
oauth.client,
user.google_access_token,
{ refresh_token: user.google_refresh_token }
)
new_token = token.refresh!
if new_token.present?
user.update(
google_access_token: new_token.token,
google_token_expire: Time.at(new_token.expires_at),
google_refresh_token: new_token.refresh_token
)
else
puts("DAMN - DIDN'T WORK!")
end
end
mail.authorization = user.google_access_token
mail
end
There is some information here, too much to list here. It may depend on the provider you are using, and their allowed usage of the refresh-token
Similarly to other answers I followed this approach, where the model storing the auth and refresh tokens is used, abstracting API interactions from that logic.
See https://stackoverflow.com/a/51041855/1392282
If you are using devise you can create a new strategy the following way I guess, so that you don't need to repeat client id and secret everywhere:
# first argument is something called app, but not sure what but nil seems to be fine.
Strategies::MyStrategy.new(nil, *Devise.omniauth_configs[:mystrategy].args)
I have been able to have a user sign in with Twitter via OmniAuth (I followed Railscast #235-6 and made a simple application). Now I am trying to display the Twitter feed of the logged in user. Can anyone tell me how this is done? How do I initialize Twitter? How do I pass in the username and password of the logged in user? I am new to Rails so it would be helpful if I knew exactly where to put the code. Thanks
First, you don't need user credentials to get a Twitter feed if it's public. Look at the
Twitter gem. Once you install the gem, all you need to do is:
require 'twitter'
Twitter.user_timeline("icambron")
Try it out in IRB to get started. Pretty easy, right?
Now, you probably want to use your API key because Twitter limits anonymous requests, and it can be problematic from a shared server. Do that in an initializer:
Twitter.configure do |config|
config.consumer_key = YOUR_CONSUMER_KEY
config.consumer_secret = YOUR_CONSUMER_SECRET
config.oauth_token = YOUR_OAUTH_TOKEN
config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
end
Get the actual values from your Twitter developer page.
Finally, to get really fancy, if you want to scale up, you can make the request on behalf of the user, using the OAuth credentials that you got from OmniAuth (NOT their username and password; you don't have those). That will allow you to make a lot more requests per second, because they're coming from different users. Just initialize Twitter with the consumer_key and consumer_secret fields set to the stuff you got from the OmniAuth hash (see here, look under "credentials" to see how to get them from OmniAuth).
class Tweet
BASE_URL = "http://api.twitter.com/1.1/statuses/user_timeline.json"
SCREEN_NAME = "OMGFacts"
MAX_TWEETS = 10000
CONSUMER_KEY = "PMiAyrY5cASMnmbd1tg"
CONSUMER_SECRET = "0TYRYg0hrWBsr1YZrEJvS5txfA9O9aWhkEqcRaVtoA"
class << self
def base_url
BASE_URL
end
def screen_name
SCREEN_NAME
end
def url(count = MAX_TWEETS)
params = {:screen_name => screen_name, :count => count}
[base_url, params.to_param].join('?')
end
def prepare_access_token(oauth_token, oauth_token_secret)
consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
{ :site => "http://api.twitter.com",
:scheme => :header,
})
# now create the access token object from passed values
token_hash = { :oauth_token => oauth_token,
:oauth_token_secret => oauth_token_secret,
:open_timeout => 500000000
}
access_token = OAuth::AccessToken.from_hash(consumer, token_hash )
return access_token
end
def get(count = MAX_TWEETS)
count = Preference.get(:2000).to_i
access_token = prepare_access_token("178394859-cJlRaiQvqVusPAPjqC2Nn7r3Uc7wWsGua7sGHzs","3T8LCZTYXzuPLGzmWX1yRnKs1JFpfJLKemoo59Piyl8")
response = JSON.parse access_token.request(:get, url).body
response[0...count]
end
end
end
I got access for the user using twitter_auth gem. Here is the code for that.
def twitter
client = TwitterOAuth::Client.new(
:consumer_key => '******',
:consumer_secret => '********'
)
request_token = client.request_token(:oauth_callback => new_user_url)
session[:request_token] = request_token
redirect_to request_token.authorize_url
end
def new
client = TwitterOAuth::Client.new(
:consumer_key => '*****',
:consumer_secret => '******'
)
access_token = client.authorize(
session[:request_token].token,
session[:request_token].secret,
:oauth_verifier => params[:oauth_verifier]
)
#For testing purpose, i tried posting a status and its working perfectly fine
client.update('I am authorized')
end
I am confused in using twitter gem cause every example from the docs says:
Twitter.user("sferik").location // throws an error, Twitter::Error::Unauthorized: Invalid / expired Token
From friends and followers
Twitter.accept("sferik") // throws an error, Twitter::Error::Unauthorized: Invalid / expired Token
Twitter.follow("sferik") // throws an error, Twitter::Error::Unauthorized: Invalid / expired Token
All these errors makes sense, cause we are applying these methods on Class not an object. But how to create an object for this. I have a authorized user but how to take actions on his profile using token we got.
Use client instead of Twitter.
You can see here how you should do it.
I am using OAuth-Ruby to do an OAuth authentication with a Tumblr application. I am able to write code that progresses through the various steps of OAuth, but I cannot get an access token or actually make a request. I can get a request key, redirect the user to Tumblr to authenticate and grant access, and receive an authenticated request key. But I can't get any farther than that.
I have registered my Tumblr application; let's assume for this question that it has provided me with the following keys:
OAuth Consumer Key: #oauth_consumer_key
Secret Key: #secret_key
(I have actual values, but I am keeping them concealed here for obvious reasons.)
I am running the following code within a controller that runs when the user submits a form, which form stores information in the #tumblog variable:
#0. provided when registering application
#key = #oauth_consumer_key
#secret = #secret_key
#site = 'http://www.tumblr.com'
#consumer = OAuth::Consumer.new(#key, #secret,
{ :site => #site,
:request_token_path => '/oauth/request_token',
:authorize_path => '/oauth/authorize',
:access_token_path => '/oauth/access_token',
:http_method => :post } )
if #consumer
#1. get a request token
#request_token = #consumer.get_request_token;
session[:request_token] = #request_token
session[:tumblog] = #tumblog
#2. have the user authorize
redirect_to #request_token.authorize_url
else
flash[:error] = "Failed to acquire request token from Tumblr."
render 'new'
end
This code gets me to the right page at Tumblr, where the user grants or denies my application access to the user's account. Assuming the user grants access, Tumblr redirects back to my application, to a callback I provided when I registered the application with Tumblr. To that point, everything works beautifully.
My OAuth callback runs the following code in the controller:
if params[:oauth_token] && params[:oauth_verifier]
#tumblog = session[:tumblog]
#request_token = session[:request_token]
#3. get an access token
#access_token = #request_token.get_access_token
. . . .
end
At Step 3, there is a problem. I cannot seem to actually get an access token with the line:
#access_token = #request_token.get_access_token
Can someone tell me what I need to do to get the access token? When I run that line, I get a OAuth::Unauthorized error.
I truly appreciate any advice. I've been Googling and trying different things for multiple days. Thanks!
i use Pelle's oauth plugin and modified it a little to support xauth like this :
require 'rubygems'
require 'oauth'
CONSUMER_KEY = 'YOUR_CONSUMER_KEY'
CONSUMER_SECRET = 'YOUR_CONSUMER_SECRET'
consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => 'https://www.tumblr.com/oauth/access_token')
access_token = consumer.get_access_token(nil, {}, { :x_auth_mode => 'client_auth',
:x_auth_username => "some#email.com",
:x_auth_password => "password"})
tumblr_credentials = access_token.get('http://www.tumblr.com/api/authenticate')
puts access_token
puts access_token.token
puts access_token.secret
puts tumblr_credentials.body
I've been spending the last few days banging my head against the wall on supporting the ability to add a contact to the Google Contacts API in my Rails 3 application. Despite many false starts, I've finally made some progress by employing the Ruby OAuth gem, and following the tutorial here: http://everburning.com/news/google-analytics-oauth-and-ruby-oh-my/
When I follow this in the console, I get further than I do in my Rails app. I can create an access token, authenticate against Google's service with the specific scope of the Contacts API, and apply the oauth_verifier token to get an access token. But when it comes time to push the data, I get this error:
response = at.post("https://www.google.com/m8/feeds/contacts/default/full", gdata)
=> #<Net::HTTPUnauthorized 401 Unknown authorization header readbody=true>
Where does the "readbody=true" header come from, and how would I get rid of it?
But it's worse in the Rails app. I have one controller action ("googlecontacts") that creates the request token and leads the user to the authentication site with Google:
def googlecontacts
#card = Card.find_by_short_link(params[:id])
#consumer = OAuth::Consumer.new(
'anonymous',
'anonymous',
{
:site => 'https://www.google.com',
:request_token_path => '/accounts/OAuthGetRequestToken',
:access_token_path => '/accounts/OAuthGetAccessToken',
:authorize_path => '/accounts/OAuthAuthorizeToken',
:signature_method => 'HMAC-SHA1',
:oauth_version => '1.0'
})
#request_token = #consumer.get_request_token(
{:oauth_callback => 'http://monkey.dev/cards/google_auth?redir='+#card.short_link},
{:scope => "https://www.google.com/m8/feeds/"}
)
session[:request_token] = #request_token
redirect_to #request_token.authorize_url
end
This appears to work; I get a working request token object, and the user is forwarded to the Google service to authenticate. The callback URL ("google_auth") should take the oauth_verifier token to create an access token. Here's the beginning of the controller:
def google_auth
#access_token = session[:request_token].get_access_token(:oauth_verifier=>params[:oauth_verifier])
And here's where it craps out. The error on that last line is:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
But the values that are in there -- the session[:request_token] and the params[:oauth_verifier] -- are present and accounted for in that action! I can't figure out what is nil here.
So I guess I need to figure out this second problem first, but bonus points for answering the first problem as well. :-)
Thanks for reading.
Aaron.
Try setting/getting the session data with a string not symbol, i.e. session["request_token"], not session[:request_token]. I know I've had that issue before in the past.
Unknown authorization header typically means that your signature didn't match what you sent. I do not recommend the oauth gem. It's full of bugs and weird issues and it doesn't properly escape certain parameters.
The Signet gem is the officially supported gem for accessing Google APIs in Ruby.
Here's how you'd implement this with Signet:
require 'signet/oauth_1/client'
require 'addressable/uri'
card = Card.find_by_short_link(params[:id])
callback = Addressable::URI.parse('http://monkey.dev/cards/google_auth')
callback.query_values = {'redir' => card.short_link}
client = Signet::OAuth1::Client.new(
:temporary_credential_uri =>
'https://www.google.com/accounts/OAuthGetRequestToken',
:authorization_uri =>
'https://www.google.com/accounts/OAuthAuthorizeToken',
:token_credential_uri =>
'https://www.google.com/accounts/OAuthGetAccessToken',
:client_credential_key => 'anonymous',
:client_credential_secret => 'anonymous',
:callback => callback
)
session[:temporary_credential] = (
client.fetch_temporary_credential!(:additional_parameters => {
:scope => 'https://www.google.com/m8/feeds/'
})
)
redirect_to(client.authorization_uri)