3

I wanted to access some google api services:

  • GDrive API
  • Contact API
  • People API

And I'm struggeling with the oauth2 impersonate service account flow (you know that one: Google Oauth v2 - service account description. For impersonification you need to apply the "Delegating domain-wide authority" in the google apps console, download the correspoding pk12 file and activate the api in a google console project.

At the moment I always get:

com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at oauthsample.GDriveAPI.<init>(GDriveAPI.java:50)
at oauthsample.GDriveAPI.main(GDriveAPI.java:85)

Here is my code:

        HttpTransport httpTransport = new NetHttpTransport();
        JacksonFactory jsonFactory = new JacksonFactory();    

        Set<String> scopes = new HashSet<String>();
        scopes.add("https://www.google.com/m8/feeds");

        GoogleCredential credential = new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountId("myserviceuser@xxxxxx.iam.account.com")
                .setServiceAccountPrivateKeyFromP12File(new File("somep12key.p12"))
                .setServiceAccountScopes(scopes)
                .setServiceAccountUser("my_user_name@gmail.com")
                .build();

       credential.refreshToken();
       ContactsService service = new ContactsService("MYAPP");
        service.getRequestFactory().setHeader("User-Agent", "MYAPP");
        service.setHeader("GData-Version", "3.0");
        service.setOAuth2Credentials(credential);

        URL feedUrl = new URL("https://www.google.com/m8/feeds/contacts/default/full");
        ContactFeed resultFeed = service.getFeed(feedUrl, ContactFeed.class);

I also searched heavily through stackoverflow (can't list all references and checked the responses and solutions). But one question was never clearly answered - nor in googles documentaiont nor on all the stackoverflow posts:

  • Is it realy possible to impersonate a serviceaccount with a normal user@gmail.com user (I mean a normal gmail account with no access to the mentioned admin console in the chapter "Delegating domain-wide authority to the service account" and withouth having a own domain )?

Some say yes, some say no. So what's the absolute truth?

As far as I understand when reading the google docs: The service account can only impersonate on users when you in charge of a own domain and you need to have a google work account with your own domain registered. Then you're able to access the admin console and can grant access to the service account.

Thanks for your patience and for your time to answer.

Best regards Matt

samatthias
  • 63
  • 2
  • 7
  • Is it really possible to impersonate a serviceaccount. doubt it maybe back when we had client login. I have never gotten it to work with Oauth. I am impressed you were able to mix discovery apis and gdata apis that's not easy :) – Linda Lawton - DaImTo Sep 15 '16 at 11:44
  • As further input I can only say that the code above is a mixture of a lot of try and error and stackoverflow solutions. Where my code breaks is at the line "credential.refreshToken();" So I guess I'm not able to get a new token for authorization. And yes you're right it's a mixture of gdata and discovery apis .. but that's not the big point here. You can instantied whatever service you want (People API, Gdrive API, ...) unless you have a correct and working oauth credential setup. – samatthias Sep 15 '16 at 12:26
  • you shouldn't need that for a service account. Note: I am not a java programmer. – Linda Lawton - DaImTo Sep 15 '16 at 12:27
  • You're right, doesn't make a lot of sence to actualy first create new credetionals and the refresh the token again. If I comment out the code with the "credential.refreshToken()" my code fails with that NPE: "Exception in thread "main" java.lang.NullPointerException: No authentication header information" on the last line. So something still not ok. Strange right? – samatthias Sep 15 '16 at 12:32

1 Answers1

5

The short answer is no, it's not possible to perform service-account impersonate of a @gmail.com account. The key reason is that although the service account OAuth flow doesn't involve an authorization screen, at the end of the day someone must still say "I authorize this application to impersonate this user."

In the case of a Google Apps domain that person is the domain administrator, who has the authority to approve apps for all users in the domain. For an @gmail.com account, there is no other authority that can approve this on your behalf. And if you have to ask the user for authorization anyway, they it just makes sense to use the regular 3-legged OAuth flow to prompt the user for authorization, get a refresh token, etc.

Now for a while there was a trick where you could take an @gmail.com user through the regular 3-legged flow, and once they approved it use the service account flow from then on. This lead to some strange problems however, so we've disabled that option. This may be why there was disagreement in the past about if this is possible.

Eric Koleda
  • 12,420
  • 1
  • 33
  • 51
  • 2
    Hi Eric Thank you for your clarification. Could Google update the OAuth docs with a sentence like: "It's not possible to impersonate a service account only with a gmail account and without having a domain registered at google."? I don't want to use your trick mentioned (the hijacked 3-legged OAuth). This is evil. – samatthias Sep 19 '16 at 06:02