4

I'm using gdata-python-client to access the Google Domain Shared Contacts API.

In enterprise applications you may want to programmatically access users data without any manual authorization on their part.

There was a protocol called 2LO (2 legged OAuth), but seems like it was linked to OAuth1 which was deprecated: "Important: OAuth 1.0 is deprecated, and registration of new OAuth 1.0 clients is closed." is all over the Oauth1 docs.

There is a new OAuth2 based recipe for "Domain-wide Delegation of Authority":

In Google Apps domains, the domain administrator can grant to third party applications domain-wide access to its users' data — this is referred as domain-wide delegation of authority. To delegate authority this way, domain administrators can use service accounts with OAuth 2.0.

This works with google-api-python-client but not with gdata-python-client.

Question: Is there any way to achieve this with Python? Seems like code from the gdata client is prehistoric - is there any other GAE runtime with a modern client library supporting delegation for the data APIs?

[update]

If I sign an httplib2 connection and call the Atom endpoint at I'm able to retrieve the feed.

http = httplib2.Http()
http = credentials.authorize(http)
resp, content = http.request(
    'https://www.google.com/m8/feeds/contacts/default/full', 'GET'
)

Unfortunately gdata-python-client uses httplib instead of httplib2.

[solved]

Perhaps I'm missing some step but looks like the token is not valid until we perform a call using httplib2. I have to run the above code BEFORE running the sample given in [aeijdenberg]'s answer, otherwise I get a 401.

Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
  • You mention GAE briefly - is this intended to be deployed on Google App Engine? If so - there's some extra work you need to do in order to get SignedJwtAssertionCredentials to function correctly. If needed, let me know and I'll dig up the latest on how to do that. – aeijdenberg Dec 19 '13 at 18:01
  • Yes, it is meant to be hosted at GAE. – Paulo Scardine Dec 19 '13 at 20:08
  • I've modified the answer to include steps for GAE. – aeijdenberg Dec 20 '13 at 18:37

1 Answers1

9

Here's an example of how to do domain-wide delegation in Python, in Google App Engine using gdata libraries:

  1. Create a project (https://cloud.google.com/console#/project).

  2. Under "APIs & Auth" enable the APIs you need to use (some gdata APIs don't appear here, if so, skip this step).

  3. Under "APIs & Auth" -> "Credentials" create a new OAuth2 client ID of type service account. Take note of the email address and client ID, and save the private key that is downloaded to a secure location.

  4. As a domain administrator go to the Admin Console (https://admin.google.com/AdminHome), navigate to "Security" -> "Advanced Settings" -> "Managed third party OAuth Client access".

  5. Paste the full client ID from earlier into the "Client Name" field, and paste in the scopes you need for your API access into the scopes field.

  6. Since we are running on Google App Engine, we need to convert the PKCS12 formatted private key to PEM format (since the PyCrypto libraries currently deployed on Google App Engine won't support PCKS12):

    cat secret-privatekey.p12 | openssl pkcs12 -nodes -nocerts -passin pass:notasecret | openssl rsa > secret-privatekey.pem
    
  7. Put this file in your app directory.

  8. Download the Google API Python client from https://code.google.com/p/google-api-python-client/downloads/list, select google-api-python-client-gae-1.2.zip.

  9. Unzip this in your app directory:

    unzip ~/Downloads/google-api-python-client-gae-1.2.zip
    
  10. Download the gdata python client from https://code.google.com/p/gdata-python-client/downloads/list, select gdata-2.0.18.zip.

  11. Install this in your app directory:

    unzip ~/Downloads/gdata-2.0.18.zip
    mv gdata-2.0.18/src/* .
    rm -rf gdata-2.0.18/
    
  12. Make sure PyCrypto is installed locally (but not in your application directory):

    sudo easy_install pycrypto
    
  13. In your app.yaml, add PyCrypto as a library:

    libraries:
    - name: pycrypto
      version: "2.6"
    
  14. Declare the following helper class:

    import httplib2
    
    class TokenFromOAuth2Creds:
      def __init__(self, creds):
        self.creds = creds
      def modify_request(self, req):
        if self.creds.access_token_expired or not self.creds.access_token:
          self.creds.refresh(httplib2.Http())
        self.creds.apply(req.headers)
    
  15. Use the private key to create a SignedJwtAssertionCredentials object:

    from oauth2client.client import SignedJwtAssertionCredentials
    
    credentials = SignedJwtAssertionCredentials(
      "<service account email>@developer.gserviceaccount.com",
      file("secret-privatekey.pem", "rb").read(),
      scope=["http://www.google.com/m8/feeds/"],
      prn="<user to impersonate>@your-domain.com"
    )
    
  16. Create a gdata client and use it:

    gd_client = gdata.contacts.client.ContactsClient('your-domain.com')
    gd_client.auth_token = TokenFromOAuth2Creds(credentials)
    xxx = gd_client.get_contacts()
    
aeijdenberg
  • 2,427
  • 1
  • 16
  • 13
  • Thanks for the heip. This gives me `Token invalid - Invalid token: Cannot parse AuthSub token: None`. I know I'm close, but have been banging my head for hours and can't figure out how to solve this. – Paulo Scardine Dec 19 '13 at 20:44
  • Can you share the code snippet that is calling the gdata libraries? – aeijdenberg Dec 20 '13 at 00:26
  • Tried your example, same result. Will update the question, thanks again. – Paulo Scardine Dec 20 '13 at 01:38
  • Oops... my bad... I don't know what I was doing wrong... it is working now!!! Many thanks, I wish I could upvote more. – Paulo Scardine Dec 20 '13 at 01:41
  • Well, I found it: won't work unless I make a call using httplib2 before I call `gdata-python-client` methods. – Paulo Scardine Dec 20 '13 at 01:49
  • Maybe try the TokenFromOAuth2Creds class that I used in this answer (even though that answer is for a different flow, the credentials object should be the same): http://stackoverflow.com/questions/19991584/unifying-oauth-handling-between-gdata-and-newer-google-apis – aeijdenberg Dec 20 '13 at 04:14
  • I was able to reproduce the issue and modified the answer. – aeijdenberg Dec 20 '13 at 18:38
  • @aeijdenberg Sorry something out of topic. I am using Git private repo.. Is it wise to store privatekey.pem there ? – Kartik Domadiya Feb 25 '14 at 14:24
  • @Kartik: never store configuration with code - instead do something like reading the key from an environment variable. – Paulo Scardine Oct 24 '14 at 16:36