Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Accounts #32

Closed
whoward opened this issue Jun 17, 2013 · 12 comments
Closed

Service Accounts #32

whoward opened this issue Jun 17, 2013 · 12 comments

Comments

@whoward
Copy link

whoward commented Jun 17, 2013

Hi everyone,
I'm just writing because I couldn't find any docs on how to use Google Service accounts with Legato (I believe I need a service account since this is used for an automated reporting process), but I kind of hacked a working solution together so I thought I would share - maybe it would make a good wiki article.

The solution uses the google-api-client gem to authorize a service account and get an access token which it then injects into the OAuth2 gem (this is a lot of unnecessary boilerplate code IMO and very hacky so please by all means if you see a way to improve it let me know!)

require 'google/api_client'

def build_legato(scope="https://www.googleapis.com/auth/analytics.readonly")
   config = config("google")

   client = Google::APIClient.new

   key = Google::APIClient::PKCS12.load_key(config["private_key"], config["private_key_passphrase"])

   service_account = Google::APIClient::JWTAsserter.new(config["email"], scope, key)

   client.authorization = service_account.authorize

   oauth_client = OAuth2::Client.new("", "", {
      :authorize_url => 'https://accounts.google.com/o/oauth2/auth',
      :token_url => 'https://accounts.google.com/o/oauth2/token'
   })

   token = OAuth2::AccessToken.new(oauth_client, client.authorization.access_token)

   Legato::User.new(token)
end

the config("google") call loads up the secure authorization data as a hash, I'll give you some dummy data to make it more clear whats going on

email: 123456789@developers.gserviceaccount.com
private_key: config/google-private-key.p12
private_key_passphrase: notasecret

So again this is very hacky and if you'd like to improve on this to make it simpler and not requiring the extra google-api-client gem I'd love to hear about it!

@tpitale
Copy link
Owner

tpitale commented Jun 17, 2013

Thanks @whoward for sharing this information! I'm not sure what a "Google Service" account is …

Does this wiki information help you? https://github.com/tpitale/legato/wiki/OAuth2-and-Google It looks very similar to the process you're using.

@whoward
Copy link
Author

whoward commented Jun 17, 2013

Hi Tony,
Yeah I had seen that wiki entry and it's not a solution for my use case - the problem here is OAuth2 generates a URL that you must physically open in the browser and click in it to authorize the app, that doesn't work for me because I am using this in a script that's run on our servers to pull GA information into our system.

A Service Account on the other hand allows you to get an access token without the use of a browser by instead providing a secure private key which is stored as a secure file on your server. Sadly as far as I could see the OAuth2 gem does not provide an easy to find method to achieve this so I had to use Google's API Client gem to do the authorization.

more information here: https://developers.google.com/accounts/docs/OAuth2ServiceAccount

@tpitale
Copy link
Owner

tpitale commented Jun 17, 2013

Oh, I see. For that, I've still used oauth 1, but with additional tokens provided by google. I don't run that script anymore, so they may have turned it off eventually.

@whoward
Copy link
Author

whoward commented Jun 18, 2013

Yeah the problem with oauth 1 is that they've deprecated it and I'd rather not have to update this later when they eventually remove it haha

@tpitale
Copy link
Owner

tpitale commented Jun 18, 2013

Absolutely! Totally makes sense. I'd love for an article in the wiki on why you'd want to use a service account and how to get that token. Any other info you'd like to gather and write out would be great. I'll be happy to review and edit as needed.

Thanks!

@tsmacdonald
Copy link

First of all, many thanks to @whoward for getting started on this!

A pretty clear use case for a service account is a stats dashboard or autogenerated report. For example, I'm using dashing to attractively present various GA data I'm trying to get with Legato.

However, I'm having a problem with the code provided:

/home/tmacdonald/.rvm/gems/ruby-2.0.0-p0/gems/oauth2-0.9.2/lib/oauth2/client.rb:110:in `request': {"errors"=>[{"domain"=>"global", "reason"=>"insufficientPermissions", "message"=>"User does not have any Google Analytics account."}], "code"=>403, "message"=>"User does not have any Google Analytics account."}:  (OAuth2::Error)
{"error":{"errors":[{"domain":"global","reason":"insufficientPermissions","message":"User does not have any Google Analytics account."}],"code":403,"message":"User does not have any Google Analytics account."}}

All my config data I pulled right from the Google API Console project...I'm pretty darn sure the user has a Google Analytics account! Any thoughts?

@tsmacdonald
Copy link

Ah. When you create the Client ID in the API Console, the email address it gives you is something like 123@developer.gserviceaccount.com. Google doesn't treat this as being equivalent to your foo@gmail.com address, so you need to go to Google Analytics and manually give the gserviceaccount account access.

I wrote something up for the wiki--I know it would've saved me a lot of time if I'd seen this issue when I was reading the OAuth page on the wiki yesterday. Feel free to edit or remove as you see fit.

It'd be nice to integrate add whoward's code to Legato proper to make it easier for end-users ...but at the same time, we don't want to add a dependency to google-api-client when we don't have to. @tpitale can make a judgment call.

@whoward
Copy link
Author

whoward commented Jul 11, 2013

You can use Ruby's OpenSSL::PKCS12 class to open and read the private key file, the only bit I don't know about is how to get a token from the private key - doing that should make it possible to drop the google-api-client gem

@tpitale
Copy link
Owner

tpitale commented Jul 15, 2013

@tsmacdonald I appreciate the article! I linked to it from the wik homepage explicitly.

I'd rather not add any specific auth code. For the most part, legato just wants you to give it the oauth2 token so it can make requests, it doesn't want to care where you get it from. I really like the approach of telling people how it can best be handled in their own code. There are so many different ways to do it, and many different needs. Making service accounts easy could be its own gem, even. 😄

@whoward If you do figure out how to get the token out of the key, please open a new issue and we can do something with that info. Or reopen this one.

Thanks everyone!

@tpitale tpitale closed this as completed Jul 15, 2013
@richardtifelt
Copy link

Hi, just implemented this Service account example, thanks for doing all the hard work!

Just one thing though, the Wiki page says the service accounts never expire, but they actually do after one hour: https://developers.google.com/accounts/docs/OAuth2ServiceAccount#expiration

I handle this expiration in my code by passing an options hash into Oauth2::AccessToken.new like so:

token = OAuth2::AccessToken.new(oauth_client, client.authorization.access_token, {
  expires_in: 1.hour
})

This allows me to look at the cached Legato user instance and determine if I need to re-authorize:

@legato_user.access_token.expired?

Would be great if you could update the wiki and example code! Thanks!

@tpitale
Copy link
Owner

tpitale commented Aug 21, 2014

Thanks @RichardJohansson, I've updated the page!

@wtfiwtz
Copy link

wtfiwtz commented Jan 29, 2015

For me, this only worked after doing the following:

    def login_service_account(scope = 'https://www.googleapis.com/auth/analytics.readonly')
      client = Google::APIClient.new(
          :application_name => 'MYAPP.COM',
          :application_version => '1.0'
      )
      key = Google::APIClient::KeyUtils.load_from_pkcs12('/PATH/TO/KEY.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 => scope,
               :issuer => 'LONG-EMAIL-ADDRESS@developer.gserviceaccount.com',
               :signing_key => key)
      client.authorization.fetch_access_token!

      oauth_client = OAuth2::Client.new('', '', {
                                              :authorize_url => 'https://accounts.google.com/o/oauth2/auth',
                                              :token_url => 'https://accounts.google.com/o/oauth2/token'
                                          })
      token = OAuth2::AccessToken.new(oauth_client, client.authorization.access_token, expires_in: 1.hour)
      Legato::User.new(token)
    end

I'm not if something has changed in google-api-client 0.8.2 or oauth2 1.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants