I'm trying to get profiles list https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/profiles/list
Here is example from web version:
Request
GET https://www.googleapis.com/analytics/v3/management/accounts/~all/webproperties/~all/profiles?key={YOUR_API_KEY}
Authorization: Bearer
X-JavaScript-User-Agent: Google APIs Explorer
Response
200 OK
- Hide headers -
Cache-Control: private, max-age=0, must-revalidate, no-transform
Content-Encoding: gzip
Content-Type: application/json; charset=UTF-8
Date: Tue, 09 Sep 2014 16:20:18 GMT
Etag: "oq4YecK1DDgQfhLS-HzmxjZUB9I/ooSCrThtdvH0a3h5ysvIA31TDu0"
Expires: Tue, 09 Sep 2014 16:20:18 GMT
Server: GSE
Transfer-Encoding: Identity
{
"kind": "analytics#profiles",
"username": "admin#domain.com",
"totalResults": 38,
"startIndex": 1,
"itemsPerPage": 1000,
"items": [
...
]
}
Here is my example ruby code using https://github.com/google/google-api-ruby-client/ gem.
def self.ga_client
client = Google::APIClient.new(
application_name: configatron.google_analytics.application_name,
application_version: configatron.google_analytics.application_version
)
key_file = File.join(configatron.google_analytics.pk12_file_path)
key = Google::APIClient::PKCS12.load_key(key_file, 'notasecret')
service_account = Google::APIClient::JWTAsserter.new(
configatron.google_analytics.service_email,
configatron.google_analytics.scope,
key
)
client.authorization = service_account.authorize
client
end
client = self.ga_client
analytics = client.discovered_api('analytics', configatron.google_analytics.version)
result = client.execute(
api_method: analytics.management.profiles.list,
parameters: {
accountId: "~all",
webPropertyId: "~all"
}
)
Response
#<Google::APIClient::Result:0x00000108c71a10 #request=#<Google::APIClient::Request:0x00000108cc3f90 #parameters={"accountId"=>"~all", "webPropertyId"=>"~all"}, #headers={"User-Agent"=>"DLM/1.0 google-api-ruby-client/0.7.1 Mac OS X/10.9.3\n (gzip)", "Accept-Encoding"=>"gzip", "Content-Type"=>""}, #api_method=#<Google::APIClient::Method:0x8474c6b8 ID:analytics.management.profiles.list>, #authenticated=nil, #authorization=#<Signet::OAuth2::Client:0x000001013435a8 #token_credential_uri=#<Addressable::URI:0x809a19e4 URI:https://accounts.google.com/o/oauth2/token>, #expiry=60, #extension_parameters={}, #additional_parameters={}, #scope=["https://www.googleapis.com/auth/analytics.readonly", "https://www.googleapis.com/auth/prediction"], #issuer="filtered#developer.gserviceaccount.com", #principal=nil, #audience="https://accounts.google.com/o/oauth2/token", #signing_key=#<OpenSSL::PKey::RSA:0x00000101341000>, #grant_type=nil, #refresh_token=nil, #code=nil, #issued_at=2014-09-09 20:19:07 +0400, #expires_in=3600, #access_token="ya29.ewBSHe0Wh5oGeKoe8aJtdpzVb-Nhr9SF0O39mdE1HgF3zTKs-8wBHL5M">, #body="">, #response=#<Faraday::Response:0x00000108c798c8 #on_complete_callbacks=[], #env=#<Faraday::Env #method=:get #body="{\"kind\":\"analytics#profiles\",\"username\":\"filtered#developer.gserviceaccount.com\",\"totalResults\":25,\"startIndex\":1,\"itemsPerPage\":1000,\"items\":...
From console it's only 25 records, why so? i'm tried to play with max-items option but have no luck, any hints?
Google API provides you options parameter
max-results: It represents itemsPerPage.Default value is 1000
start-index: Its represent the page no. Default value is 1
Hence I don't think changing values of max-items(max-results) will help because you have only 35 records. Using google console we are using different account(xyz#gmail.com) & for API we are using(XXX#developer.gserviceaccount.com)
I tried with above options using google client and its shows me correct result.
How I do it?
I have total 13 GA accounts(xyz#gmail.com).
Given 11 account analytics readonly permission to XXX#developer.gserviceaccount.com
Tried using GA Client library its shows me totalResults=11 with its item detail(s).
Checked my profile results with OAuth 2.0 permission using Views (Profiles): list play. It shows me totalResults=13 records
Cross verified the google client API records by checking service name. It shows me the same service which I have granted access for developer email account XXX#developer.gserviceaccount.com.
I am sure you have followed below steps. Please cross verify it once again.
Go to the Google API Console and create a new project
In the API Access tab, click Create an OAuth2.0 Client ID
Select the Service account option and press Create client ID
Download private key
Copy the Service Account Email address i.e. XXXX##developer.gserviceaccount.com
Visit your GA Admin and add this email as a user to your properties
This is a must; you'll get cryptic errors otherwise.
Give Google Analytics Permission to my project using Google Enable Api
Get the latest Google Ruby Client API via Gem
source "https://rubygems.org"
gem 'google-api-client', '>= 0.6'
Your API access Code. Mine is as below
require 'google/api_client'
require 'json'
API_VERSION = 'v3'
CACHED_API_FILE = "analytics-#{API_VERSION}.cache"
# Update these to match your own apps credentials
service_account_email = 'XYZ#developer.gserviceaccount.com'
key_file = 'client.p12' # File containing your private key
key_secret = 'notasecret' # Password to unlock private key
client = Google::APIClient.new(
:application_name => 'Plus Test', #Application name
:application_version => '1')
# Load our credentials for the service account
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, key_secret)
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/analytics','https://www.googleapis.com/auth/analytics.edit','https://www.googleapis.com/auth/analytics.readonly'],
:issuer => service_account_email,
:signing_key => key)
# Request a token for our service account
client.authorization.fetch_access_token!
analytics = nil
# Load cached discovered API, if it exists. This prevents retrieving the
# discovery document on every run, saving a round-trip to the discovery service.
if File.exists? CACHED_API_FILE
File.open(CACHED_API_FILE) do |file|
analytics = Marshal.load(file)
end
else
analytics = client.discovered_api('analytics', API_VERSION)
File.open(CACHED_API_FILE, 'w') do |file|
Marshal.dump(analytics, file)
end
end
result = client.execute(:api_method => analytics.management.profiles.list, :parameters => {
'alt' => "json",
'accountId' => "~all",
'webPropertyId' => "~all",
'fields' => 'items(id,name,permissions,websiteUrl),itemsPerPage,startIndex,totalResults,username',
'max-results' => 1, #used to get number of records. Default value is 1000
'start-index' => 1 #This is page number. Default value is 1
})
#Response result
result = JSON.parse(result.data.to_json)
print result
Execute code using bundle exec ruby test.rb
Above code print the json of associated accounts.
Conclusion: Probably you have missed to give GA permission to some properties/accounts for developer email. Please cross verify the Actual GA Account(xyz#gmail.com) profile list with XXX#developer.gserviceaccount.com
Related
I have a firebase project which Im trying to authenticate from my rails server creating a custom token with the library ruby-jwt as it says on the docs, but i keep getting the same error:
auth/invalid-custom-token, The custom token format is incorrect. Please check the documentation.
The credentials.json is from the service account I made in google console, uid is sent from the front end to the api.
def generate_auth_token(uid)
now_seconds = Time.now.to_i
credentials = JSON.parse(File.read("credentials.json"))
private_key = OpenSSL::PKey::RSA.new credentials["private_key"]
payload = {
:iss => credentials["client_email"],
:sub => credentials["client_email"],
:aud => 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
:iat => now_seconds,
:exp => now_seconds+(60*60), # Maximum expiration time is one hour
:uid => uid.to_s,
:claims => {:premium_account => true}
}
JWT.encode(payload, private_key, 'RS256')
end
it looks like this in jwt.io
{
"iss": "defered#defered.iam.gserviceaccount.com",
"sub": "defered#defered.iam.gserviceaccount.com",
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat": 1486824545,
"exp": 1486828145,
"uid": "4",
"claims": {
"premium_account": true
}
}
It looks like the accepted answer found a way to link authentication from Firebase to Rails, but the original question seems to be asking how to link Rails authentication to Firebase (which is what I was trying to do).
To keep your authentication logic in Rails (ex: from Devise) and share it with Firebase, first get a Firebase server key as a .json file from your Service Accounts page in your project's settings.
You'll only need the private_key and client_id from this file, which I recommend storing as environment variables so they're not potentially leaked in source code.
Next, make a Plain ol' Ruby object (PORO) that will take in a User and spit out a JSON Web Token (JWT) that Firebase can understand:
class FirebaseToken
def self.create_from_user(user)
service_account_email = ENV["FIREBASE_CLIENT_EMAIL"]
private_key = OpenSSL::PKey::RSA.new ENV["FIREBASE_PRIVATE_KEY"]
claims = {
isCool: "oh yeah"
}
now_seconds = Time.now.to_i
payload = {
iss: service_account_email,
sub: service_account_email,
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
iat: now_seconds,
exp: now_seconds + (60*60), # Maximum expiration time is one hour
uid: user.id,
# a hash to pass to the client as JSON
claims: claims
}
JWT.encode payload, private_key, "RS256"
end
end
Now send this JWT to authenticated users through javascript in your application layout:
window.firebaseJWT = "#{FirebaseToken.create_from_user(current_user)}";
In your frontend code, you can now use this token to authenticate users:
firebase
.auth()
.signInWithCustomToken(window.firebaseJWT)
.catch(error => {
console.error(error);
});
Remember to sign them out of firebase when they sign out of your application:
firebase
.auth()
.signOut()
.then(() => {
// Sign-out successful.
})
.catch(error => {
console.error(error);
});
I found a better way to authenticate, I'm just sending the token that firebase gives you and verifying it on rails with the information I need and that's it.
Check if your secret key is wrapped in double quotes and not single as they contain '\n' escape sequences. An auth/invalid-custom-token error is thrown if the secret key is not as specified in the documentation.
I am new to Rails and I want to export data to Google Spread Sheet from my web application.
I have created an app to get client id and client secret and enabled drive api for that.
I have installed google drive and google api client gem
And I used the code stated here
This code successfully runs, open a new tab for authorization, and displays a code to paste. This is the point where I am stuck. The code that google authorization demands is in my controller code so my user can paste that code in my controller. I know its quiet stupid thing to ask but I am not finding a way to automatically get the code from api to further execution as we usually do in our facebook oauth applications. So can you guide me how to do it? The code looks like
def export_spred_sheet
require 'rubygems'
require 'google/api_client'
require 'launchy'
# Get your credentials from the console
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Create a new API client & load the Google Drive API
client = Google::APIClient.new
drive = client.discovered_api('drive', 'v2')
# Request authorization
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.scope = OAUTH_SCOPE
client.authorization.redirect_uri = REDIRECT_URI
uri = client.authorization.authorization_uri
Launchy.open(uri)
# Exchange authorization code for access token
$stdout.write "Enter authorization code: "
client.authorization.code = gets.chomp
client.authorization.fetch_access_token!
# Insert a file
file = drive.files.insert.request_schema.new({
'title' => 'My document',
'description' => 'A test document',
'mimeType' => 'text/plain'
})
media = Google::APIClient::UploadIO.new('document.txt', 'text/plain')
result = client.execute(
:api_method => drive.files.insert,
:body_object => file,
:media => media,
:parameters => {
'uploadType' => 'multipart',
'alt' => 'json'})
# Pretty print the API result
jj result.data.to_hash
end
Or is there any other way to do the task If I am on wrong track?
I was also fighting against this in one of my project and finally I found soulution as follows:
Instead of using client ID and client secrete you can use P12 key generated in google developer console under service account for authentication. In this case you won't need to paste any code in controller.
To generate p12 key
go to Google developer console
then -> "APIs & Auth" -> "Credentials"
Create New Client ID of type 'Service Account'
Once new client Id generated. Under Service Account section you will find a button to 'generate new P12 key'. On click it will generate a p12 key. Download it and store it securely in your app and use it for authentication.
And use following code snippet to fetch access token.
key_file = p12_key_file_path
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, 'notasecret')
client = Google::APIClient.new application_name: "abcd", application_version: "1.0.0"
SERVICE_ACCOUNT_ID (used in following code snippet) will be Email address generated under this "Service Account" section in google developer console.
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/analytics',
:issuer => SERVICE_ACCOUNT_ID,
:access_type => 'offline',
:signing_key => key
)
client.authorization.fetch_access_token!
client
I hope it will help you.
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 am currently in Step 3 of the processing on getting an oauth token/secret from an user trying to login via Twitter. https://dev.twitter.com/docs/auth/implementing-sign-twitter
Step 3 tells me to send this request to the API, but I am stuck as to how to do so. I currently have BOTH the oauth_token and oauth_verifier, but how do I send this POST request to get the oauth_token, oauth_token_secret pair?
Is there a standard Oauth Ruby gem I can use to send this POST request? I see examples online where I pass an #accessToken object, but i do not have such an object available. I just have the oauth_token and oauth_verifier (as strings). Given these 2 things, how do I convert them to an oauth_token and oauth_token_secret?
POST /oauth/access_token HTTP/1.1
User-Agent: themattharris' HTTP Client
Host: api.twitter.com
Accept: */*
Authorization: OAuth oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
oauth_nonce="a9900fe68e2573b27a37f10fbad6a755",
oauth_signature="39cipBtIOHEEnybAR4sATQTpl2I%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318467427",
oauth_token="NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0",
oauth_version="1.0"
Content-Length: 57
Content-Type: application/x-www-form-urlencoded
oauth_verifier=uw7NjWHT6OJ1MpJOXsHfNxoAhPKpgI8BlYDhxEjIBY
Try something like the following rails controller actions, using the twitter and oauth gems:
def redirect
consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, {
:site => "https://api.twitter.com",
:scheme => :header
})
request_token = consumer.get_request_token(:oauth_callback => CALLBACK_URL)
session[:twitter_request_token] = request_token
redirect_to request_token.authorize_url #=> "https://api.twitter.com/oauth/authorize?oauth_token=XYZ"
end
def callback
request_token = session[:twitter_request_token]
access_token = request_token.get_access_token(:oauth_verifier => params[:oauth_verifier])
client = Twitter::REST::Client.new(
:consumer_key => CONSUMER_KEY,
:consumer_secret => CONSUMER_SECRET,
:access_token => access_token.token,
:access_token_secret => access_token.secret
)
twitter_user = client.user
redirect_to root_url # or do something with the twitter_user
end
See also: http://barkingiguana.com/2009/10/13/twitter-oauth-authentication-using-ruby/
yes there is the Omniauth gem for authentication with Twitter. The documentation is straight forward.
I personally use Omniauth integrated with Devise and the Twitter gem to access Twitter - works very well.
Hope this helps,
Eugen
The common procedure is the following:
You shell to register your app on twitter development page.
Then set the proper Name, Description, and Website values up for your application.
App Name
App Description
http://your_app_domain.zone:3000/
Change Application Type is your app, by default it has read only access type.
Setup the callback URL for yuor application:
http://your_app_domain.zone:3000/auth/twitter/callback
Store the all keys, and secrets that are shewn on the OAuth tool twitter page:
Consumer key:
Consumer secret:
Access token:
Access token secret:
Setup route on your site with devise, or devise-like gem with the specified twitter keys, and secrets to enable authentication engine. The route list now shall include /auth/twitter path.
By going to http://your_app_domain.zone:3000/auth/twitter you will be redirected to twitter site, and dropped back to your site with passed oauth_token
But
You simple receive those keys, and secrets, and apply then in your app, avoiding the 6, and 7 points:
client = Twitter::REST::Client.new do |config|
config.consumer_key = "YOUR_CONSUMER_KEY"
config.consumer_secret = "YOUR_CONSUMER_SECRET"
config.access_token = "YOUR_ACCESS_TOKEN"
config.access_token_secret = "YOUR_ACCESS_SECRET"
end
The following is the code I am using to allow users to allow users to authorise my app to access their Google Calendar via OAuth. I based it off this sample code.
It works most of the time, but sometimes, there is an ArgumentError: Missing authorization code error on the client.authorization.fetch_access_token! line in the create_google_calendar action in the services controller. If I comment out that line, all of the client.authorization attributes are null.
I am using Rails 3.2.0 and Ruby 1.9.2.
What is causing this?
Gemfile
gem 'google-api-client', :require => 'google/api_client'
service.rb
def self.google_calendar_client google_calendar_service=nil
client = Google::APIClient.new
client.authorization.client_id = xxx
client.authorization.client_secret = xxx
client.authorization.scope = 'https://www.googleapis.com/auth/calendar'
url_prefix = Rails.env.production? ? xxx : 'http://localhost:3000'
client.authorization.redirect_uri = "#{url_prefix}/create_google_calendar"
if google_calendar_service.present?
client.authorization.update_token! :access_token => google_calendar_service.token, :refresh_token => google_calendar_service.google_calendar_refresh_token, :expires_in => google_calendar_service.google_calendar_expires_in, :issued_at => Time.at(google_calendar_service.google_calendar_issued_at)
client.authorization.fetch_access_token! if client.authorization.expired?
end
client
end
services_controller.rb
def connect_google_calendar
#google_calendar_url = Service.google_calendar_client.authorization.authorization_uri.to_s
end
def create_google_calendar
client = Service.google_calendar_client
client.authorization.code = params[:code]
client.authorization.fetch_access_token!
current_user.services.create :provider => 'google_calendar', :token => client.authorization.access_token, :google_calendar_refresh_token => client.authorization.refresh_token, :google_calendar_expires_in => client.authorization.expires_in, :google_calendar_issued_at => client.authorization.issued_at
end
The truth is, I don't know. Your code looks right to me. But I can at least tell you what the error means. Missing authorization code means that it thinks you're trying to do an "authorization code" grant type when you fetch the access token. If you're actually trying to obtain an access token off a refresh token as opposed to doing it on the first pass after obtaining authorization from the user, then you may not have correctly set up the authorization object.
You can check this by inspecting the client.authorization.grant_type value. In very recent versions of the client you can manually set the grant_type value to force a particular mode, which may give you more informative error messages, depending on what the actual issue is.