Search code examples
javaoauth-2.0google-oauthgoogle-oauth-java-client

Using Java and OAuth 2.0 to Connect to Google Contacts


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 = "[email protected]";
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("[email protected]")
                                                    .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("[email protected]"), 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.


Solution

  • 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 = "[email protected]";
    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("[email protected]")
          .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/[email protected]/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/[email protected]/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());
        }
    }