1

I am having issues connecting to the Google Contacts API with Java using OAuth 2.0. After extensive searching, I have reached the point where it appears I can connect, but receive error messages in the final process.

I have done the following:

1) Created an OAuth 2.0 Service account via Google's Developers Console for the project.

2) Following the steps to 'Delegate domain-wide authority to the service account' I found at https://developers.google.com/identity/protocols/OAuth2ServiceAccount, I have authorized the Client ID of the service account created in step #1 above to the scope https://www.google.com/m8/feeds/ for the Contacts (Read/Write).

3) Here is the code I have based on other members posts:

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.gdata.client.contacts.ContactsService;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;

public class Connector {
private static ContactsService contactService = null;
private static HttpTransport httpTransport;

private static final String APPLICATION_NAME = "MyProject";
private static final String SERVICE_ACCOUNT_EMAIL = "service-account-email-address@developer.gserviceaccount.com";
private static final java.util.List<String> SCOPE = Arrays.asList("https://www.google.com/m8/feeds");

private Connector() {
    // explicit private no-args constructor
}

public static ContactsService getInstance() {
    if (contactService == null) {
        try {
            contactService = connect();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return contactService;
}

private static ContactsService connect() throws GeneralSecurityException, IOException {
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();

    java.io.File p12File = new java.io.File("MyProject.p12");

    // @formatter:off
    GoogleCredential credential = new GoogleCredential.Builder()
                                                    .setTransport(httpTransport)
                                                    .setJsonFactory(JacksonFactory.getDefaultInstance())
                                                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                                                    .setServiceAccountScopes(SCOPE)
                                                    .setServiceAccountPrivateKeyFromP12File(p12File)
                                                    .setServiceAccountUser("user@example.com")
                                                    .build();
    // @formatter:on

    if (!credential.refreshToken()) {
        throw new RuntimeException("Failed OAuth to refresh the token");
    }

    ContactsService myService = new ContactsService(APPLICATION_NAME);
    myService.setOAuth2Credentials(credential);

    return myService;
}
}

In theory, I believe this is supposed to all work. However, when credential.refreshToken() is executed I receive the error:

com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
{
  "error" : "access_denied",
  "error_description" : "Requested client not authorized."
}

If I remove setServiceAccountUser("user@example.com"), I receive the error message com.google.gdata.util.ServiceForbiddenException: Cannot request contacts belonging to another user. I believe I need to keep setServiceAccountUser(...) but am unable to get past the token response exception.

I am unsure where to go from here.

Thank you for any advice or assistance you can provide.

Terry
  • 169
  • 5
  • 18
  • Ok, change of direction, slightly. How can I gain access to the contacts for just one of the Google Apps users using 'service account flow' such that user interaction is not required? That is all I essentially need. Do I need to add additional scopes instead of simply the Contacts API? – Terry May 30 '15 at 16:43

2 Answers2

4

For anyone interested or running into the same issue as I was having, I have come across this link which solved the issue. The main problem was the '/' I had at the end of the scope I needed to use. It needed to be added in my code to match the scope I gave to the service account API access.

My final code:

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

String APPLICATION_NAME = "MyProject";
String SERVICE_ACCOUNT_EMAIL = "my-service-account@developer.gserviceaccount.com";
java.io.File p12File = new java.io.File("MyProject.p12");

GoogleCredential credential = new GoogleCredential.Builder()                                                                    
      .setTransport(httpTransport)                                                     
      .setJsonFactory(jsonFactory)                                                   
      .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)   
      .setServiceAccountScopes(
            Collections.singleton("https://www.google.com/m8/feeds/"))                                                          
      .setServiceAccountPrivateKeyFromP12File(p12File)                                                  
      .setServiceAccountUser("user@example.com")
      .build();

if (!credential.refreshToken()) {
    throw new RuntimeException("Failed OAuth to refresh the token");
}

ContactsService service = new ContactsService(APPLICATION_NAME);
service.setOAuth2Credentials(credential);

Query gQuery = new Query(new java.net.URL("https://www.google.com/m8/feeds/groups/user@example.com/full"));
gQuery.setMaxResults(32767);
ContactGroupFeed groupFeed = service.query(gQuery, ContactGroupFeed.class);

for (ContactGroupEntry group : groupFeed.getEntries()) {
    System.out.println("group: " + group.getTitle().getPlainText());

    Query cQuery = new Query(new java.net.URL("https://www.google.com/m8/feeds/contacts/user@example.com/full"));
    cQuery.setMaxResults(32767);
    String grpId = group.getId();
    cQuery.setStringCustomParameter("group", grpId);
    ContactFeed feed = service.query(cQuery, ContactFeed.class);

    for (ContactEntry contact : feed.getEntries()) {
        System.out.println("name: " + contact.getTitle().getPlainText());
    }
}
Terry
  • 169
  • 5
  • 18
  • I was following your code.but got stuck here with complie error := The method `setOAuth2Credentials(GoogleCredential)` is undefined for the type `ContactsService` Please reply back soon – Yogesh Seralia Aug 11 '15 at 12:28
  • You may have older version of the GData classes in your classpath. I believe they used to be found in the gdata-*.jar archives. The class (com.google.gdata.client.contacts.ContactService) that I used can be found in the core-1.47.1.jar archive. – Terry Aug 11 '15 at 17:09
  • no issues with classpath because I have `gdata-contacts-3.0.jar` for it.Now checking your options of adding core-1.47.1.jar......... – Yogesh Seralia Aug 11 '15 at 17:28
  • one more question for you : i am gettting this error on running above code in main() method : `com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request { "error" : "invalid_grant" } ` – Yogesh Seralia Aug 12 '15 at 06:01
  • Sorry for the delay...busy in other tasks. I am not 100% positive about this issue, but the 'invalid grant' suggests it might be the configuration within Google. You might want to check that. I believe there are ways to also test connection to the service using a Google app. This might help to isolate the issue. – Terry Aug 12 '15 at 19:02
1

It appears you're using the Authorization Code flow from OAuth2 (read more in this nice post).

In this case, after the step you already did (register your app in Google Console + register your app to access Contacts API), your app will need to:

  1. Provide to the user the authorization URL: user will click and grant to your app permission to read the data you need.
  2. With user permission granted, you'll receive an authorization code.
  3. Using this authorization code, you'll exchange it by a token.
  4. Now, with the token in your hands, call the Google Contacts API.

User Java Libraries

My advice is to use popular Java libraries that handle all this flow for you.

Two popular libraries are:

Both have Maven support.

Another possible solution is to use broad address book services like CloudSponge.com, which offers a Java API and support for other popular sources, in case you're planning beyond just Gmail.

Rael Gugelmin Cunha
  • 3,327
  • 30
  • 25
  • After a little more research, I find that I want to use 'Service Account Flow' and impersonate a user on the domain as per: https://developers.google.com/api-client-library/java/google-api-java-client/oauth2#service_accounts and according to this: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority it should work... There seems to be a piece missing where Google does not accept me wanting to access the user's contacts. – Terry May 29 '15 at 01:55