3
votes

I am trying to read/write emails/folders inside Gmail mailboxes using the Gmail REST API. When adding the following Google auth scopes, emails can be read from Gmail REST API without any problem:

https://apps-apis.google.com/a/feeds/compliance/audit/, https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/admin.directory.group.member.readonly, https://www.googleapis.com/auth/admin.directory.group.readonly

Note: The parameter https://www.googleapis.com/auth/gmail.readonly correctly allows one to read from mailboxes.

However, I need to be able to delete emails too. Thus, in line with the documentation at https://developers.google.com/gmail/api/auth/scopes?hl=ja, one simply needs to include https://mail.google.com/ in place of https://www.googleapis.com/auth/gmail.readonly. When adding the following auth scopes:

https://apps-apis.google.com/a/feeds/compliance/audit/, https://www.googleapis.com/auth/admin.directory.user.readonly, https://mail.google.com/, https://www.googleapis.com/auth/admin.directory.group.member.readonly, https://www.googleapis.com/auth/admin.directory.group.readonly

... the error outputted is as follows:

    2015-07-27 10:27:59 i.c.s.a.cv [DEBUG] failed get labels for user
    com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
    {
       "error" : "access_denied",
       "error_description" : "Requested client not authorized."
    }

Surely, this is incorrect on the part of Google? What am I missing? Is the documentation incorrect? What auth scope needs to be added?

I am interfacing with the Java Google API Client Library. See: https://developers.google.com/api-client-library/java/google-api-java-client/reference/1.20.0/overview-summary

The delete request is as follows:

public void deleteMessages(Queue<String> messages, GoogleUserAdapter user) throws Exception {

    Gmail gmail = getService(user);

    JsonBatchCallback<Void> voidCallBack = new JsonBatchCallback<Void>() {
        @Override
        public void onSuccess(Void t, HttpHeaders responseHeaders) throws IOException {
            logger.debug("delete success");

        }
        @Override
        public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
            logger.debug("failed to delete message:"+e.getMessage());
        }
    };
    while (!messages.isEmpty()) {
        if (Thread.currentThread().isInterrupted()) 
            throw new InterruptedException();

        BatchRequest batch = gmail.batch();
        for (int i = 0; i < MAX_REQUESTS; i++) {
            if (messages.isEmpty() || Thread.currentThread().isInterrupted()) 
                break;
            gmail.users().messages().delete(user.getId(), messages.poll()).queue(batch, voidCallBack);
        }

        batch.execute();
    }
}

The credential is created as follows:

   private GoogleCredential getCredentials(JsonFactory jsonFactory, HttpTransport httpTransport, String impersonateAccount) throws Exception {

    Preconditions.checkNotNull(Strings.emptyToNull(impersonateAccount), "Google impersonate account is null");
     Preconditions.checkNotNull(Strings.emptyToNull(connection.getServiceAccountId()), "Service Account Email address is null");
    Preconditions.checkNotNull(connection.getServiceAccountPrivateKey(), "Service Account Private Key is null");

    GoogleCredential credential = new GoogleCredential.Builder()
          .setTransport(httpTransport)
          .setJsonFactory(jsonFactory)
          .setServiceAccountId(connection.getServiceAccountId())
          .setServiceAccountScopes(
                    Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY, GmailScopes.MAIL_GOOGLE_COM,
                            "https://apps-apis.google.com/a/feeds/compliance/audit/",
                            DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER_READONLY, 
                            DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY))
          .setServiceAccountUser(impersonateAccount)
          .setServiceAccountPrivateKey(connection.getServiceAccountPrivateKey().getPrivateKey())
          .build();
    setHttpTimeout(credential);
    return credential;
}

The exact error that occurs on delete is:

   failed to delete message:Insufficient Permission

Jamie

2
Do you get this error when you try to delete a mail, or when you try to set the scopes?Tholle
When trying to delete a mail, I receive the access denied error. Any ideas?jamie
In fact, the auth scope mail.google.com seems to have no effect on permissions.... all read/write access is denied. Any ideas?jamie
I don't think it is scope related. I think you are supplying your access_token in the wrong way. Could you show us your DELETE-request?Tholle
Thank you for your assistance. Please refer to edited article.jamie

2 Answers

6
votes

Access denied is caused by a typo in the constant GmailScopes.MAIL_GOOGLE_COM as defined by the Google Java Client API.

The constant returns "https://mail.google.com" and not "https://mail.google.com/" (as it ought to be). Omitting a backslash at the end of the string will result in access denied.

Thus, in the example above, the following service scopes must be set:

https://apps-apis.google.com/a/feeds/compliance/audit/","https://mail.google.com/",DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER_READONLY, DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY, DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY

(note: the hardcoded value of "https://mail.google.com/")

The following string must be added to Manage API client access page in Google Apps:

https://apps-apis.google.com/a/feeds/compliance/audit/, https://www.googleapis.com/auth/admin.directory.user.readonly, https://mail.google.com/, https://www.googleapis.com/auth/admin.directory.group.member.readonly, https://www.googleapis.com/auth/admin.directory.group.readonly

I hope this helps someone else!

0
votes
private GoogleCredential getCredentials(JsonFactory jsonFactory, HttpTransport httpTransport, String impersonateAccount) throws Exception {

    Preconditions.checkNotNull(Strings.emptyToNull(impersonateAccount), "Google impersonate account is null");
     Preconditions.checkNotNull(Strings.emptyToNull(connection.getServiceAccountId()), "Service Account Email address is null");
    Preconditions.checkNotNull(connection.getServiceAccountPrivateKey(), "Service Account Private Key is null");

    GoogleCredential credential = new GoogleCredential.Builder()
          .setTransport(httpTransport)
          .setJsonFactory(jsonFactory)
          .setServiceAccountId(connection.getServiceAccountId())
          .setServiceAccountScopes(
                    Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY, GmailScopes.MAIL_GOOGLE_COM,
                            "https://apps-apis.google.com/a/feeds/compliance/audit/",
                            DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER_READONLY, 
                            DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY))
          .setServiceAccountUser(impersonateAccount)
          .setServiceAccountPrivateKey(connection.getServiceAccountPrivateKey().getPrivateKey())
          .build();
    setHttpTimeout(credential);
    return credential;
}