2
votes

I'm trying to implement a new service to our application using Google Drive Java API v3 that is responsible for uploading files to a specific folder in Google Team Drive. I use the service account that my company has created specifically for this project and also generated a JSON file from Google Developer Console that contains a private key. I have also shared the folder to the service account using its email [email protected] and granted rights of Content Manager to the shared Team Drive. Furthermore, G Suite domain-wide of authority has not been granted to this service account for a few reasons.

What am I trying to achieve here:
I want to build and return an authorized Google Drive client service using the service account’s generated private key and therefore be able to send the requests to upload files to the folder in Google Team Drive.

What I currently use:

  • IntelliJ IDEA IntelliJ IDEA 2018.1.7 (Ultimate Edition)
  • Spring Boot 2
  • Java 10.0.1

What is the problem:
I’m unable to return the authorized Google Drive client service successfully and the requests to upload files are not being sent at all. What makes it more confusing is that there are no exceptions thrown. However, a credential is returned successfully with the access token and an expiration time.

What I have already read/found:
Using OAuth2.0 for Server to Server Application: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Java Quickstart on creating simple requests to the Drive API: https://developers.google.com/drive/api/v3/quickstart/java

JavaDoc reference for the Drive API: https://developers.google.com/resources/api-libraries/documentation/drive/v3/java/latest/

How to upload file to google drive with service account credential: How to upload file to google drive with service account credential

How to access Team Drive using service account with Google Drive .NET API v3: How to access Team Drive using service account with Google Drive .NET API v3

Authentication to upload files in my drive using Google drive API client library for Java Authentication to upload files in my drive using Google drive API client library for Java

What I have already tried:

  • Using GoogleCredential-class to return the credential (that class appeared to be deprecated: https://googleapis.dev/java/google-api-client/latest/)
  • Finding replacement for the deprecated class by looking into Github projects and tutorials
  • Update and check all the missing dependencies in pom.xml
  • Checked the settings of the shared Team Drive to make sure it is shared with the service account
  • Added logs to make sure the problem is really what I described above
  • Tried Java Quickstart on creating simple requests to the Drive API-tutorial (however, that turned out to be not completely suitable for our project's needs)

The relevant part of ContractStateUpdateService.java:

File fileMetadata = new File();
fileMetadata.setName(fileTitle);
// setting the id of folder to which the file must be inserted to
fileMetadata.setParents(Collections.singletonList("dumbFolderId"));
fileMetadata.setMimeType("application/pdf");

byte[] pdfBytes = Base64.getDecoder().decode(base64File.getBytes(StandardCharsets.UTF_8));
InputStream inputStream = new ByteArrayInputStream(pdfBytes);

// decoding base64 to PDF and its contents to a byte array without saving the file on the file system
InputStreamContent mediaContent = new InputStreamContent("application/pdf", inputStream);

logger.info("Starting to send the request to drive api");
File file = DriveUtils.getDriveService().files().create(fileMetadata, mediaContent).execute();
logger.info("Succesfully uploaded file: " + file.getDriveId());

DriveUtils.java:

public class DriveUtils {

    private static final String APPLICATION_NAME = "Google Drive Service";

    // setting the Drive scope since it is essential to access Team Drive
    private static List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE);

    // private key is stored at the root of the project for now
    private static String PRIVATE_KEY_PATH = "/path/to/private_key.json";
    private static final Logger logger = LoggerFactory.getLogger(DriveUtils.class);

    // build and return an authorized drive client service
    public static Drive getDriveService() throws IOException, GeneralSecurityException {
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        GoogleCredentials credentials;

        try (FileInputStream inputStream = new FileInputStream(PRIVATE_KEY_PATH)){
            credentials = ServiceAccountCredentials.fromStream(inputStream).createScoped(SCOPES);
            credentials.refreshIfExpired();
            AccessToken token = credentials.getAccessToken();
            logger.info("credentials: " + token.getTokenValue());
        } catch (FileNotFoundException ex) {
            logger.error("File not found: {}", PRIVATE_KEY_PATH);
            throw new FileNotFoundException("File not found: " + ex.getMessage());
        }

        logger.info("Instantiating client next");
        // Instantiating a client: this is where the client should be built but nothing happens... no exceptions!
        Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, (HttpRequestInitializer) credentials)
                .setApplicationName(APPLICATION_NAME)
                .build();
        // this log should appear immediately after the client has been instantiated but still nothing happens
        logger.info("Client instantiated");

        return service;
    }

}

pom.xml:

<!-- https://mvnrepository.com/artifact/com.google.api-client/google-api-client -->
        <dependency>
            <groupId>com.google.api-client</groupId>
            <artifactId>google-api-client</artifactId>
            <version>1.29.2</version>
        </dependency>

        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-drive</artifactId>
            <version>v3-rev165-1.25.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http -->
        <dependency>
            <groupId>com.google.auth</groupId>
            <artifactId>google-auth-library-oauth2-http</artifactId>
            <version>0.16.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.oauth-client/google-oauth-client-jetty -->
        <dependency>
            <groupId>com.google.oauth-client</groupId>
            <artifactId>google-oauth-client-jetty</artifactId>
            <version>1.29.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

I'm sure I'm missing something here and I apologize for my English in advance. Any help will be appreciated.

1
uh! I am flattered after seeing your detailed question. My god! so much details. Therefore, upvoted.surajs1n
one such thing happened with me when I was using their TranslationClient and that was because my system time was not synchronized and the token was expired even before use. This could be a reason for you as well although it is just a guess based on my experience.Navjot Singh

1 Answers

1
votes

Thank you for your comments, the advices here have been helpful and were worth to look into. However, the solution I'm going to present here isn't going to answer to my question directly on how or why my code didn't produce any error messages. So for now, this is my workaround solution to the problem:

  1. Enabling Drive API. After reading the good portion of articles and documentations on making requests from service account to Drive API, it became clear that my code wasn't going to work if we didn't have Drive API enabled from Google API Console.
  2. Downgrading the versions of three dependencies to 1.23.0.

pom.xml:

 <dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client</artifactId>
    <version>1.23.0</version>
 </dependency>
 <dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-drive</artifactId>
    <version>v3-rev110-1.23.0</version>
 </dependency>
 <dependency>
    <groupId>com.google.oauth-client</groupId>
    <artifactId>google-oauth-client-jetty</artifactId>
    <version>1.23.0</version>
 </dependency>
  1. Setting the value of property setSupportsTeamDrive to true. Without the property we wouldn't have been able to save files to the shared folder in Team Drive at all.

ContractStateUpdateService.java:

File fileMetadata = new File();
fileMetadata.setName(fileTitle);

// setting the id of folder to which the file must be inserted to
fileMetadata.setParents(Collections.singletonList("dumbTeamDriveId"));
fileMetadata.setMimeType("application/pdf");

// decoding base64 to PDF and its contents to a byte array without saving the file on the file system
byte[] pdfBytes = Base64.getDecoder().decode(base64File.getBytes(StandardCharsets.UTF_8);
InputStream inputStream = new ByteArrayInputStream(pdfBytes);
InputStreamContent mediaContent = new InputStreamContent("application/pdf", inputStream);

try {
  // upload updated agreement as a PDF file to the Team Drive folder
  DriveUtils.getDriveService().files().create(fileMetadata, mediaContent)
                            .setSupportsTeamDrives(true) // remember to set this property to true!
                            .execute();
} catch (IOException ex) {
  logger.error("Exception: {}", ex.getMessage());
  throw new IOException("Exception: " + ex.getMessage());
} catch (GeneralSecurityException ex) {
  logger.error("Exception: {}", ex.getMessage());
  throw new GeneralSecurityException("Exception: " + ex.getMessage());
}
  1. Splitting the method to make logic more clearer.

Updated code from DriveUtils-class:

// create and return credential
private static Credential getCredentials() throws IOException {
    GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream(PRIVATE_KEY_PATH))
                .createScoped(SCOPES);

    return credential;
}

// build and return an authorized drive client service
public static Drive getDriveService() throws IOException, GeneralSecurityException {
    final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

    // Instantiating a client
    Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials())
                .setApplicationName(APPLICATION_NAME)
                .build();

    return service;
}