I'm using Google API in Java and have the following code snippets.
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.List;
import java.lang.Object;
import java.util.ArrayList;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
public class DriveQuickstart {
private static final String APPLICATION_NAME = "DriveQuickstart";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final String TOKENS_DIRECTORY_PATH = "tokens";
// * Global instance of the scopes required by this quickstart.
// * If modifying these scopes, delete your previously saved tokens/ folder.
private static final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE_METADATA_READONLY);
private static final String CREDENTIALS_FILE_PATH = "C:/Quickstart/src/main/resources/client_secret.json";
// * Creates an authorized Credential object.
// * #param HTTP_TRANSPORT The network HTTP Transport.
// * #return An authorized Credential object.
// * #throws IOException If the client_secret.json file cannot be found.
// */
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStream in = new FileInputStream(CREDENTIALS_FILE_PATH);
if (in == null) {
throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
}
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
public static void main(String[] args) throws IOException, GeneralSecurityException {
try
{
// Build a new authorized API client service.
System.out.println("This is a test line");
//declare final variable NetHttpTransport and GoogleNetHttpTransport from import statements
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
System.out.println("This is the second test line");
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build();
System.out.println("This is the third test line");
// Print the names and IDs for files.
FileList result = service.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files == null || files.isEmpty()) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (File file : files) {
System.out.printf("%s (%s)\n", file.getName(), file.getId());
}
}
Drive.Files var1 = service.files();
System.out.println("var1 = " + var1.toString());
Drive.Files.List var2 = var1.list();
if (var2 == null)
System.out.println("null");
else {
System.out.printf("Var2 size is: %d\n", var2.size() );
}
//iterate through map and print results
}
catch(Exception e){
System.out.println("error");
}
System.out.println("This is the fourth test line");
}
}
}
To go more into detail. I'm using a somewhat unconventional method to try it. I'm using only command line to run (no IDEs, no gradle).
Here is a link to an ss with my output in command prompt.
cmd output
Assuming that I am actually accessing the metadata in Google Drive, and that the Drive.Files is nested inside an Abstract Map (java.util.AbstractMap), I should be able to return the size of the map to get an idea of where it it searching by how many files it is returning.
I'm not sure what is going on. First time I compile and run, I get the tab opening in chrome asking for permission to gain access to metadata to drive.
Second time, it gives the "WARNING: unable to change permissions for everybody: " errors.
On top of that, var1 ('set to service.files()') returns com.google.api.services.drive.Drive$Files#458ad742 when using toString().
The subclass service.files().list() assigned to var2 should return a list of files in my Google Drive, but apparently it is not doing so.
I may be misunderstanding a lot of stuff, so bear with me. Thanks for the help.
In case people ask, I have tried the tutorial here
https://developers.google.com/drive/api/v3/quickstart/java
It opened the tab to ask for metadata access to google drive on whichever google account I selected.
storedcredential
So from that alone, I (think) connected to Google Drive.
However, the default code that is supposed to list the names and ids of 10 files doesn't do anything.
How am I running the program:
Here is the batch file that I'm using to compile and run the code in cmd.
jar cfe DriveQuickstart.jar Quickstart DriveQuickstart.class
javac -cp ./* DriveQuickstart.java
java -cp ./* DriveQuickstart
For some reason I don't know/understand, this was the only way that wouldn't return methods from the imported jar files not existing.
Familiarize yourself with the Java Quickstart to see how to get the results of the API calls.
Problems
1 - setPermissionsToOwnerOnly
I'm not sure what is going on. First time I compile and run, I get the tab opening in chrome asking for permission to gain access to metadata to drive. Second time, it gives the "WARNING: unable to change permissions for everybody: " errors.
This seems to be related to this issue. It could mean that the folder you are using to run your code from has different permissions than expected.
Does this happen if you place your code on your Desktop (as example) and then run it from there?
2 - toString()
On top of that, var1 ('set to service.files()') returns com.google.api.services.drive.Drive$Files#458ad742 when using toString(). The subclass service.files().list() assigned to var2 should return a list of files in my Google Drive, but apparently it is not doing so.
This weird looking string is how Java prints out an instance of a class.
com.google.api.services.drive.Drive$Files#458ad742
<NAMESPACE>$<CLASS>#<HASHCODE>
The fact that the toString method prints this out means that this method wasn't overwritten for this specific Class, so its calling the general Object.toString() method.
To print out what you want to see, try the nested methods.
Related
I am using a service account to access google doc files of users in my enterprise google account using impersonation.
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 a java program that should reproduce the problem:
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.
Is this expected? How can I make this stop? Is there another method in the API I can call to download the google docs without updating this timestamp?
Also opened a ticket on the github for the google drive java sdk: https://github.com/googleapis/google-api-java-client-services/issues/3160
Updating the viewedByMeTime field upon calling the endpoint is indeed intended behaviour. Any action performed through the API is considered the same way as if the user did that action manually (i.e. that field would be updated too when the user visits the document through the UI).
By using domain-wise delegation (or "user impersonation"), you have no way to avoid this issue.
The only workaround would be to give the service account access to this file, and let it export the file without domain-wide delegation. The viewedByMeTime field will be updated only for the service account itself, but not for the original owner of that file (or any other user having access to it).
I am new to utilizing Docker. I have a modified version of Google Sheet's "QuickStart" tutorial Java program within a Gradle Build (It has been modified to write to a few cells rather than reading cells, the Sheet ID has been changed too...the original Quickstart link is here: https://developers.google.com/sheets/api/quickstart/java ). The Java program works on its own, but I intend write a Dockerfile that will successfully build an image from the Gradle Build I currently have so it can be run in a container. Since Dockerfile syntax is nuanced and versatile, and because a Google Sheets Gradle Build has Google dependancies, I'm having trouble figuring out what to do here. Note that the original application uses a "basic gradle build" (you type "gradle init --type basic" to make it), and the Java code is in a src/main/java directory while the necessary credentials.json file (containing my client ID and secret so Google Sheets will work) is in src/main/resources. I'm also using Gradle 5.2.1 .
Here is the Java code...
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.sheets.v4.Sheets;
import com.google.api.services.sheets.v4.SheetsScopes;
import com.google.api.services.sheets.v4.model.ValueRange;
import com.google.api.services.sheets.v4.model.UpdateValuesResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
public class SheetsQuickstart {
private static final String APPLICATION_NAME = "Google Sheets API Java Quickstart";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final String TOKENS_DIRECTORY_PATH = "tokens";
/**
* Global instance of the scopes required by this quickstart.
* If modifying these scopes, delete your previously saved tokens/ folder.
*/
private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS);
private static final String CREDENTIALS_FILE_PATH = "/credentials.json";
/**
* Creates an authorized Credential object.
* #param HTTP_TRANSPORT The network HTTP Transport.
* #return An authorized Credential object.
* #throws IOException If the credentials.json file cannot be found.
*/
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStream in = SheetsQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
//System.out.println("The getCredentials will be returned");
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
//Writes a few basic things to cells in a google sheet
public static void main(String... args) throws IOException, GeneralSecurityException {
// Build a new authorized API client service.
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
final String spreadsheetId = "16WoDySciZRhD3QSH2_V1bk5kf_IFCcrPe49NE8WRxVg";
Sheets service = new Sheets.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build();
ValueRange body = new ValueRange().setValues(getData());
UpdateValuesResponse result =
service.spreadsheets().values().update(spreadsheetId, "B1", body)
.setValueInputOption("RAW")
.execute();
}
public static List<List<Object>> getData() {
List<Object> data1 = new ArrayList<Object>();
data1.add("test1");
data1.add("test2");
data1.add("Row");
List<Object> data2 = new ArrayList<Object>();
data2.add("test3");
data2.add("test4");
data2.add("test5");
List<Object> data3 = new ArrayList<Object>();
data3.add("test6");
List<List<Object>> data = new ArrayList<List<Object>>();
data.add(data1);
data.add(data2);
data.add(data3);
return data;
}
}
My build.gradle file looks like this...
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'SheetsQuickstart'
sourceCompatibility = 1.7
targetCompatibility = 1.7
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.api-client:google-api-client:1.23.0'
compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0'
compile 'com.google.apis:google-api-services-sheets:v4-rev516-1.23.0'
}
Again, I used a "grovvy basic gradle build" and the directory structure can be found at https://guides.gradle.org/creating-new-gradle-builds/ near the beginning (also I added the src/main directory in the gradle directory's root). If I'm to "dockerize" this gradle build, what are my best available options in doing so? Would it only require one dockerfile, and is there an example of what it might look like? Are there any things fundamentally wrong with how I am approaching this? Thanks.
I am trying to add code to my android app that gets information from the UI and appends to a file in google drive. I have gotten as far sign in and authorization, as well as querying the file by name. Most of the SO threads I have read are for the old API or REST API.
However, I want to open it in Read/Write, or Write Only mode. I have tried looking at the quickstart demos and the drive API, but none of them are helpful.
How can I get the driveId programatically? I have tried getting the ID from drive itself. Is there a way to build Metadata that gets the ID from the query?
If I use openFile(DriveId.decodeFromString(actual_id).asDriveFile()
I get the following error:
java.lang.IllegalArgumentException: Invalid DriveId: **actual_id**
Is getting the file ID from the sharing link wrong: drive.google.com/open?id=some_id
If so, how can I achieve this?
private String id = "1fuTq1Q6MHrchgW7sZImjvSfpAShHhsbx";
private DriveFile file = DriveId.decodeFromString(id).asDriveFile();
private void getFile() {
Query q = new Query.Builder().addFilter(Filters.and(Filters.eq(SearchableField.TITLE, "HelloWorld.txt"))).build();
}
private void appendFile() {
Task<DriveContents> openTask = getResourceClient().openFile(file, DriveFile.MODE_READ_WRITE);
openTask.continueWithTask(task -> {
DriveContents driveContents = task.getResult();
ParcelFileDescriptor pfd = driveContents.getParcelFileDescriptor();
long bytesToSkip = pfd.getStatSize();
try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
// Skip to end of file
while (bytesToSkip > 0) {
long skipped = in.skip(bytesToSkip);
bytesToSkip -= skipped;
}
}
try (OutputStream out = new FileOutputStream(pfd.getFileDescriptor())) {
out.write("Hello world".getBytes());
}
// [START drive_android_commit_contents_with_metadata]
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setStarred(true)
.setLastViewedByMeDate(new Date())
.build();
Task<Void> commitTask =
getResourceClient().commitContents(driveContents, changeSet);
// [END drive_android_commit_contents_with_metadata]
return commitTask;
})
.addOnSuccessListener(this,
aVoid -> {
//showMessage(getString(R.string.content_updated));
Log.i("DRIVE", "Sucess");
finish();
})
.addOnFailureListener(this, e -> {
Log.e(TAG, "Unable to update contents", e);
// showMessage(getString(R.string.content_update_failed));
finish();
});
}
Also the file exists and the ID is valid.. Apparently
According to the documentation for the GDAA (Google Drive Android API), it should be possible to download a file based on its ID alone by using asDriveFile().
To do that you need a query and then store the information in a Task<MetadataBuffer>, and then should be able to files.get(0).asDriveFile() in the method where you are attempting to download by FileId. But even when pulling the metadata and use the query method, you are greeted with IllegalArgumentException invalid DriveId (which IS THE SAME ID, so it was never invalid), But it STILL shows it as invalid. I got tired of wrestling with it and went to the REST API.
Things to note: The file you are downloading MUST BE: Doc/Spreadsheet/Slides, photo or apps script.
You can choose the type of file you want to export it as. Here is the "truth table" for compatibility.
From this example it is easy enough to write your data and re-upload it. However, these files have a special encoding so you can't write the data directly.
Depending on what you need to accomplish, you can use the sheets api or
apache poi
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [START drive_quickstart]
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.List;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
public class DriveQuickstart {
private static final String APPLICATION_NAME = "Google Drive API Java Quickstart";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final String TOKENS_DIRECTORY_PATH = "tokens";
private static String fileId = "super_secret_string";
private static final String OUTPUT = "super_secret_path";
/**
* Global instance of the scopes required by this quickstart.
* If modifying these scopes, delete your previously saved credentials/ folder.
*/
private static final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE); //DONT USE THIS SCOPE IN PRODUCTION!
private static final String CREDENTIALS_FILE_PATH = "/credentials.json";
/**
* Creates an authorized Credential object.
* #param HTTP_TRANSPORT The network HTTP Transport.
* #return An authorized Credential object.
* #throws IOException If the credentials.json file cannot be found.
*/
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStream in = DriveQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}
public static void main(String... args) throws IOException, GeneralSecurityException {
// Build a new authorized API client service.
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build();
// Print the names and IDs for up to 10 files.
FileList result = service.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files == null || files.isEmpty()) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (File file : files) {
System.out.printf("%s (%s)\n", file.getName(), file.getId());
}
}
//Download the file from it's known ID
FileOutputStream fos = new FileOutputStream(OUTPUT);
service.files().export(fileId, "text/plain").executeMediaAndDownloadTo(fos);
//Append some data to the file
FileWriter fw = new FileWriter(OUTPUT, true);
BufferedWriter bw = new BufferedWriter(fw);
bw.newLine();
bw.write("Goodbye, World!");
bw.newLine();
bw.close();
}
}
// [END drive_quickstart]
I went the Java Quickstart and got a working application that just shows the full list of all the users connected to the account logged into. I have gone over the Java docs and the only thing I have found that pertains to the deletion of a user is the "setDeletionTime" in the User class, but I have tried that with a dummy account and set the time to "null" and tried to create a time that was set to today and neither worked for deleting the user. I have no clue what I am missing here.
Code I am using, most of it copied from the google quickstart
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledAp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
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.api.client.json.JsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.admin.directory.DirectoryScopes;
import com.google.api.services.admin.directory.model.*;
import com.google.api.services.admin.directory.Directory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
public class Quickstart {
/** Application name. */
private static final String APPLICATION_NAME =
"Directory API Java Quickstart";
/** Directory to store user credentials for this application. */
private static final java.io.File DATA_STORE_DIR = new java.io.File(
System.getProperty("user.home"), ".credentials/admin-directory_v1-java-quickstart");
/** Global instance of the {#link FileDataStoreFactory}. */
private static FileDataStoreFactory DATA_STORE_FACTORY;
/** Global instance of the JSON factory. */
private static final JsonFactory JSON_FACTORY =
JacksonFactory.getDefaultInstance();
/** Global instance of the HTTP transport. */
private static HttpTransport HTTP_TRANSPORT;
/** Global instance of the scopes required by this quickstart.
*
* If modifying these scopes, delete your previously saved credentials
* at ~/.credentials/admin-directory_v1-java-quickstart
*/
private static final List<String> SCOPES =
Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY);
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
}
/**
* Creates an authorized Credential object.
* #return an authorized Credential object.
* #throws IOException
*/
public static Credential authorize() throws IOException {
// Load client secrets.
/* This does not work as of now
InputStream in = Quickstart.class.getResourceAsStream("src/resources/client_secret.json");
*/
InputStream in = new FileInputStream("src/resources/client_secret.json");
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(DATA_STORE_FACTORY)
.setAccessType("offline")
.build();
Credential credential = new AuthorizationCodeInstalledApp(
flow, new LocalServerReceiver()).authorize("user");
System.out.println(
"Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
return credential;
}
/**
* Build and return an authorized Admin SDK Directory client service.
* #return an authorized Directory client service
* #throws IOException
*/
public static Directory getDirectoryService() throws IOException {
Credential credential = authorize();
return new Directory.Builder(
HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
public static void main(String[] args) throws IOException {
// Build a new authorized API client service.
Directory service = getDirectoryService();
// Print the first 10 users in the domain.
Users result = service.users().list().setCustomer("my_customer").setOrderBy("email").execute();
List<User> users = result.getUsers();
if (users == null || users.size() == 0) {
System.out.println("No users found.");
} else {
for (User user : users) {
//This is where I tried to delete the users
//I have also tried using a normal for loop and nothing changes
that
System.out.println();
}
}
}
After reading everything I could find on this that google had to offer I finally figure it out.... I think. I am going to explain it here and have this as the answer because it worked; however, if I am doing something wrong then please tell me. Anyway, here is what I did:
So first thing's first. Every action (as far as I could tell) goes through http as a URL coupled with a command. This means in order for anything to happen you have to have a transport (given by the HttpTransport class) and a factory (given by the HttpRequestFactory class) to create the HttpRequest object that holds the action/command.
The request we will be making is the DELETE request shown here under "Delete a user account"
This can all be done by doing something like this:
HttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
HttpRequestFactory HTTP_REQUEST_FACTORY = HTTP_TRANSPORT.createRequestFactory();
HttpRequest deleteRequest = HTTP_REQUEST_FACTORY.buildDeleteRequest(new GenericUrl("https://www.googleapis.com/admin/directory/v1/users/userkey"));
BUT WAIT! We are missing a very important key element here. We have to supply the Factory with the correct credentials to mark the header. Basically it is to tell google that we are able to delete the user. So how do we do that?
First we must set the scope, what we want access to. Our scope is ADMIN_DIRECTORY_USER. Set the scope like this: (MAKE SURE YOU DELETE THE FILE IN THE .credentials DIRECTORY IF YOU HAVE ALREADY RAN THIS PROGRAM!!!!)
List<String> SCOPES = Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER);
Next we need to have a credentials object. This can be done by using the method google gives us in their quickstart (the authorize method). To give the credentials to our factory we simply edit the line above by passing it the credentials object:
HttpRequestFactory HTTP_REQUEST_FACTORY = HTTP_TRANSPORT.createRequestFactory(credentials);
Note: do not pass it a HttpRequestInitializer from the credentials.getRequestInitializer() method as this is null (At least for me, this could be with just the way I am doing it but I rather not try it).
Here I will attach my code to show you a completed version of this:
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.admin.directory.DirectoryScopes;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
public class Quickstart {
/** Application name. */
private static final String APPLICATION_NAME = "Deleting user example";
/** Directory to store user credentials for this application. */
private static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"), ".credentials/admin-directory_v1-java-quickstart");
/** Global instance of the {#link FileDataStoreFactory}. */
private static FileDataStoreFactory DATA_STORE_FACTORY;
/** Global instance of the JSON factory. */
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
/** Global instance of the HTTP transport. */
private static HttpTransport HTTP_TRANSPORT;
//This creates the factory that is used for the user made requests
private static HttpRequestFactory HTTP_REQUEST_FACTORY;
//This is the credentials for the entire application
private static Credential credential;
/** Global instance of the scopes required by this quickstart.
*
* If modifying these scopes, delete your previously saved credentials
* at ~/.credentials/admin-directory_v1-java-quickstart
*/
private static final List<String> SCOPES = Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER);
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
}
/**
* Creates an authorized Credential object.
* #return an authorized Credential object.
* #throws IOException
*/
public static Credential authorize() throws IOException {
// Load client secrets.
/* This does not work as of now
InputStream in = Quickstart.class.getResourceAsStream("src/resources/client_secret.json");
*/
InputStream in = new FileInputStream("src/resources/client_secret.json");
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES).setDataStoreFactory(DATA_STORE_FACTORY).setAccessType("offline").build();
Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
return credential;
}
public static void main(String[] args) throws IOException {
System.out.println("Deleting user with email");
credential = authorize();
HTTP_REQUEST_FACTORY = HTTP_TRANSPORT.createRequestFactory(credential);
HttpRequest deleteRequest = HTTP_REQUEST_FACTORY.buildDeleteRequest(new GenericUrl("https://www.googleapis.com/admin/directory/v1/users/REPLACEMEWITHEMAILORUSERKEY"));
deleteRequest.execute();
}
}
I am writing a back-end process in Java that will impersonate a user and add/remove documents on their Google Drive.
The server account seems to authenticate correctly but when I try to impersonate a user, I get a 401 Unauthorized error. Please see below for details.
Configuration
I have configured the server account as follows:
Created a project under Google APIs and enabled Google Drive API
Created a service account called anothertest#yyyyyyyyy.iam.gserviceaccount.com, set the role as Service Account Actor and given it domain-wide delegation. It has Client ID 110xxxxxxxxx342
I have download the P12 key file
I have configured the domain using the Manage API client access screen to authorize 110xxxxxxxxx342 to have the scope: https://www.googleapis.com/auth/drive.
Google Support have looked at my configuration and have given it the thumbs up.
My code then looks as follows:
package com.dcm.sharingdocuments;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import com.google.api.client.auth.oauth2.TokenErrorResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;
public class SharingDocumentsTest3 {
private static final String SERVICE_ACCOUNT_EMAIL = " anothertest#yyyyyyyyy.iam.gserviceaccount.com";
public static Drive getDriveService(String userEmail) throws Exception {
File keyFile = new File("E:\\Projects\\Workspace\\Sharing Documents\\authentication\\AnotherTestKeyFile.p12");
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY);
GoogleCredential credential = null;
if (userEmail == null) {
credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKeyFromP12File(keyFile).build();
credential.refreshToken();
} else {
credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKeyFromP12File(keyFile).setServiceAccountUser(userEmail).build();
credential.refreshToken();
}
Drive service = new Drive.Builder(httpTransport, jsonFactory, null).setHttpRequestInitializer(credential)
.build();
return service;
}
public static void main(String[] args) {
SharingDocumentsTest3 sdt3 = new SharingDocumentsTest3();
sdt3.execute();
}
private void execute() {
try {
Drive service = getDriveService(null);
Drive services = getDriveService("anzzzze#zzzzz.me.uk");
displayFiles(services);
} catch (Exception e) {
e.printStackTrace();
}
}
private void displayFiles(Drive service) throws Exception {
FileList result = service.files().list().setPageSize(10).execute();
List<com.google.api.services.drive.model.File> files = result.getFiles();
if (files == null || files.size() == 0) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (com.google.api.services.drive.model.File file : files) {
Set<Entry<String, Object>> entries = file.entrySet();
Iterator<Entry<String, Object>> it = entries.iterator();
while (it.hasNext()) {
Entry<String, Object> entry = it.next();
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String) {
System.out.println("\tKey = " + key + ", Value = " + (String) value);
} else {
System.out.println("\tKey = " + key + ", Value = " + value.toString());
}
}
System.out.printf("%s (%s)\n", file.getName(), file.getId());
}
}
}
}
When I run the code as is above, I get the error:
Mar 29, 2017 9:55:27 AM com.google.api.client.googleapis.services.AbstractGoogleClient <init>
WARNING: Application name is not set. Call Builder#setApplicationName.
com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at com.dcm.sharingdocuments.SharingDocumentsTest3.getDriveService(SharingDocumentsTest3.java:50)
at com.dcm.sharingdocuments.SharingDocumentsTest3.execute(SharingDocumentsTest3.java:75)
at com.dcm.sharingdocuments.SharingDocumentsTest3.main(SharingDocumentsTest3.java:65)
So the code fails at credential.refreshToken() when I set the setServiceAccountUser. It appears to have successfully refreshed the token when I do not. I have tried various combinations of this code – e.g. commented out the refreshToken() lines, commented out the getDriveService(null) line – but whenever I try to use/refresh the credential obtained for the impersonated user I get the 401 Unauthorized error.
If I modify the code so that the drive obtained by getDriveService(null) is passed to DisplayFiles(...), then I get one file listed called “Getting Started”. So it seems that the service account authorization is working and Google have added their default file to the Drive for the server account.
I am using google-*1.22.0.jar files and Java 1.8 to the run the above code
The problem I think is in the way I have configured the domain or the way I am trying to impersonate the user but my code looks as many examples on the web do and Google Support appear to say that I have configured the domain correctly.
Anything you can suggest as a resolution or next step would be much appreciated!
I have been stuck on this problem for a long time and I finally found my problem. There is definitely a bug in the "Manage API client access" Admin console...
You must put the "Client ID" (e.g. 110xxxxxxxxx342) for the client name and NOT the "Service Account ID" (the one that looks like an email). Now, their documentation is correct, and they do say in the documentation to use the Client ID, I have to give them that.
So here is the bug. When I arrived to the Manage API screen, I saw "Example: www.example.com". I typed in the Service Account ID there, thinking that the email address format matched "www.example.com" better than the Client ID. I pressed "Authorize", and the entry had clearly been accepted and everything was good. The result looks like this:
It even generated the Client ID from the Service ID! Great! Except my code gets a 401 error every time I try to connect with setServiceUser().
If I return to the Manage API Client Access console and if I remove the previous entry and perform the same actions except use the Client ID instead of the Service ID. The result is this:
Exactly the same, but now I don't get a 401 error. So there is NO WAY to look at the console, and know if you have it successfully configured it or not. I tested this 3 times to make sure I wasn't losing my mind...