googleapis / google-api-java-client-services

Generated Java code for Google APIs
Apache License 2.0
625 stars 353 forks source link

When using service account impersonation, when calling export on Google Docs using v3 API, viewedByMeTime timestamp is updated #3160

Open nddipiazza opened 4 years ago

nddipiazza commented 4 years ago

I am using a service account to access google doc files of users in my enterprise google account.

See:

https://developers.google.com/drive/api/v3/about-auth#OAuth2Authorizing

So far so good.

Then, I need to download contents of Google Docs.

When calling Google Drive API to download the contents of a Google Doc, the documentation says to run the following:

https://developers.google.com/drive/api/v3/manage-downloads

Here is an example of my java code line that does this:

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.SecurityUtils;
import com.google.api.services.drive.Drive;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;

public class FetchGoogleDocContentsWithServiceAccount {
  static int readTimeout = 60000;
  static int connectTimeout = 60000;
  static String serviceAccountId = "";
  static String serviceAccountEmail = "";
  static String serviceAccountPrivateKeyFile = "";
  static String serviceAccountPrivateKeyFilePassword = "";
  static String fileId = "";
  static JacksonFactory jacksonFactory = new JacksonFactory();
  static NetHttpTransport httpTransport = new NetHttpTransport();
  static List<String> googleScopeList = Arrays.asList("https://www.googleapis.com/auth/drive.readonly",
      "https://www.googleapis.com/auth/admin.directory.group.readonly",
      "https://www.googleapis.com/auth/admin.directory.user.alias.readonly",
      "https://www.googleapis.com/auth/admin.directory.group", "https://www.googleapis.com/auth/admin.directory.user",
      "https://www.googleapis.com/auth/drive");

  public static void main(String[] args) throws Exception {
    Drive drive = (new Drive.Builder(httpTransport,
        jacksonFactory,
        getRequestInitializer(getGoogleCredentials())))
        .setApplicationName("Sample app").build();

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    drive.files().export(fileId, "application/vnd.google-apps.document")
        .executeMediaAndDownloadTo(baos);

    System.out.println(baos.toString("UTF-8"));
  }

  public static HttpRequestInitializer getRequestInitializer(final GoogleCredential requestInitializer) {
    return httpRequest -> {
      requestInitializer.initialize(httpRequest);
      httpRequest.setConnectTimeout(readTimeout);
      httpRequest.setReadTimeout(connectTimeout);
    };
  }

  public static GoogleCredential getGoogleCredentials() {
    GoogleCredential credential;

    try {
      GoogleCredential.Builder b = new GoogleCredential.Builder().setTransport(httpTransport)
          .setJsonFactory(jacksonFactory).setServiceAccountId(serviceAccountId)
          .setServiceAccountPrivateKey(SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),
              new FileInputStream(new File(serviceAccountPrivateKeyFile)), serviceAccountPrivateKeyFilePassword,
              "privatekey", serviceAccountPrivateKeyFilePassword))
          .setServiceAccountScopes(googleScopeList);
      if (serviceAccountEmail != null) {
        b = b.setServiceAccountUser(serviceAccountEmail);
      }
      credential = b.build();
    } catch (IOException | GeneralSecurityException e1) {
      throw new RuntimeException("Could not build client secrets", e1);
    }
    return credential;
  }
}

When I have performed this operation, we are seeing that the viewedByMeTime field is actually being updated as the impersonated user.

This is not good, because now people think someone might have stolen access to their account. They are going to open tickets with the security team.

There needs to be a way to download the contents of a Google Doc file while not updating this timestamp.

roblucar commented 4 years ago

This issue causes an undue burden on IT support since users raise a security concern when the impersonated user access Google Docs. The issue is easily reproducible and should be addressed. Thanks