How can we setup a service account authorisation with the Google Drive API, so we can act on behalf of a other user within the domain.
The code throws a HTTP ERROR 500 on line 201, see stacktrace below:
URI is not hierarchical
Caused by:
java.lang.IllegalArgumentException: URI is not hierarchical
at java.io.File.<init>(File.java:418)
at com.caase.portal.DriveServlet.getDriveService(DriveServlet.java:201)
at com.caase.portal.DriveServlet.doGet(DriveServlet.java:67)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at com.google.api.client.extensions.servlet.auth.oauth2.AbstractAuthorizationCodeServlet.service(AbstractAuthorizationCodeServlet.java:130)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
See the code mentioned below:
/** Email of the Service Account */
private static final String SERVICE_ACCOUNT_EMAIL = "xxx#developer.gserviceaccount.com";
/** Path to the Service Account's Private Key file */
private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "xxx-privatekey.p12";
/**
* Build and returns a Drive service object authorized with the service accounts
* that act on behalf of the given user.
*
* #param userEmail The email of the user.
* #return Drive service object that is ready to make requests.
*/
public static Drive getDriveService(String userEmail) throws GeneralSecurityException,
IOException, URISyntaxException {
URI keyURL;
keyURL = DriveServlet.class.getResource(SERVICE_ACCOUNT_PKCS12_FILE_PATH).toURI();
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(DriveScopes.all())
.setServiceAccountUser(userEmail)
.setServiceAccountPrivateKeyFromP12File(new java.io.File(keyURL))
.build();
Drive service = new Drive.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).build();
return service;
}
The line that throws the error is:
.setServiceAccountPrivateKeyFromP12File(new java.io.File(keyURL))
If I split up the line the error occurs in the new File creation:
new java.io.File(keyURL)
The key file is located in: src/main/resources
Any help is welcome :-)
Moved the resources folder from src/main/resources to src/resources and changed SERVICE_ACCOUNT_PKCS12_FILE_PATH to '/resources/xxx-privatekey.p12'. It works like a charm now!
Related
I have a java agent that will list all the files from Google Drive using the api provided from google developer (https://developers.google.com/drive/api/v3/quickstart/java).
However, the agent is having a "Access is denied" error when trying to create the "tokens" folder from "C:\Program Files (x86)\IBM\Notes\framework" directory.
I have created the "tokens" folder manually but the error still occured.
I have put all the required jar files in "/jvm/lib/ext" and updated the java policy on my local as follows:
// custom java security policy, to allow external jar file access
grant { permission java.util.PropertyPermission "http.keepAlive", "read, write"; };
grant { permission java.security.AllPermission; }
My actual agent code
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";
/**
* 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 = "/credentials.json";
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
this.main();
// (Your code goes here)
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* 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 = JavaAgent.class.getResourceAsStream(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() 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());
}
}
}
}
This is the error Ive'd got from the java debug console.
java.io.IOException: Access is denied.
at java.io.File.createNewFile(File.java:894)
at com.google.api.client.util.store.FileDataStoreFactory$FileDataStore.(FileDataStoreFactory.java:96)
at com.google.api.client.util.store.FileDataStoreFactory.createDataStore(FileDataStoreFactory.java:73)
at com.google.api.client.util.store.AbstractDataStoreFactory.getDataStore(AbstractDataStoreFactory.java:55)
at com.google.api.client.auth.oauth2.StoredCredential.getDefaultDataStore(StoredCredential.java:171)
at com.google.api.client.auth.oauth2.AuthorizationCodeFlow$Builder.setDataStoreFactory(AuthorizationCodeFlow.java:744)
at com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow$Builder.setDataStoreFactory(GoogleAuthorizationCodeFlow.java:209)
at JavaAgent.getCredentials(Unknown Source)
at JavaAgent.main(Unknown Source)
at JavaAgent.NotesMain(Unknown Source)
at lotus.domino.AgentBase.runNotes(Unknown Source)
at lotus.domino.NotesThread.run(Unknown Source)
there are already a few people that have asked questions in regards to this, but I still wasn't able to solve my problem.
So, I did the gradle/java quickstart to Print the names and IDs for up to 10 files in my Googledrive. That was fine, but I want to write a java script to upload files on my drive, I inserted the main function below in my java file.
public class DriveCommandLine {
/** Application name. */
private static final String APPLICATION_NAME =
"Drive 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/drive-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/drive-java-quickstart
*/
private static final List<String> SCOPES =
Arrays.asList(DriveScopes.DRIVE_METADATA_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.
InputStream in =
DriveCommandLine.class.getResourceAsStream("/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 Drive client service.
* #return an authorized Drive client service
* #throws IOException
*/
public static Drive getDriveService() throws IOException {
Credential credential = authorize();
return new Drive.Builder(
HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
public static void main(String[] args) throws IOException{
//Insert a file
Drive service = getDriveService();
File body = new File();
body.setDescription("A test document");
body.setMimeType("text/plain");
java.io.File fileContent = new java.io.File("D:\\MyFiles\\nkonstantinidis\\Study\\IntelliJ_Shortcuts.txt");
FileContent mediaContent = new FileContent("text/plain", fileContent);
File file = service.files().insert(body, mediaContent).execute();
System.out.println("file ID: " + file.getId());
}
}
And I'm getting an error message shown below when I run it
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Insufficient Permission"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:432)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
at DriveCommandLine.main(DriveCommandLine.java:114)
I have created the OAuth client ID and enabled google drive but this still doing it's thing.
Could you maybe help me, or even better direct to tutorial for uploading files on google drive, I'm quite bad in Java so bear with me.
Thanks in advance.
You must change SCOPE used for connection (Insufficient Permission message) into appropriate one
private static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY);
From DriveScope.class:
/** View and manage the files in your Google Drive. */
public static final String DRIVE = "https://www.googleapis.com/auth/drive";
/** View and manage its own configuration data in your Google Drive. */
public static final String DRIVE_APPDATA = "https://www.googleapis.com/auth/drive.appdata";
/** View and manage Google Drive files and folders that you have opened or created with this app. */
public static final String DRIVE_FILE = "https://www.googleapis.com/auth/drive.file";
/** View and manage metadata of files in your Google Drive. */
public static final String DRIVE_METADATA = "https://www.googleapis.com/auth/drive.metadata";
/** View metadata for files in your Google Drive. */
public static final String DRIVE_METADATA_READONLY = "https://www.googleapis.com/auth/drive.metadata.readonly";
/** View the photos, videos and albums in your Google Photos. */
public static final String DRIVE_PHOTOS_READONLY = "https://www.googleapis.com/auth/drive.photos.readonly";
/** View the files in your Google Drive. */
public static final String DRIVE_READONLY = "https://www.googleapis.com/auth/drive.readonly";
/** Modify your Google Apps Script scripts' behavior. */
public static final String DRIVE_SCRIPTS = "https://www.googleapis.com/auth/drive.scripts";
Remember to delete saved credentials (created by application) after changing SCOPE
I try getting list of Google Drive files from my application (simple Google account)(https://developers.google.com/identity/protocols/OAuth2ServiceAccount)
List with OAuth2 authorization work perfect: https://developers.google.com/drive/v3/reference/files/list#try-it
I've create service account with delegation to Google Apps domain (https://console.developers.google.com/iam-admin/serviceaccounts):
and download json service key.
Apply to GD from application:
private static HttpTransport HTTP_TRANSPORT;
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) throws IOException {
Credential credential = GoogleCredential
.fromStream(new FileInputStream(".\\config\\google_drive_api.json"))
.createScoped(DriveScopes.all());
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName("javawebinar-1314").build();
FileList result = service.files().list().execute();
List<File> files = result.getFiles();
System.out.println(files.size());
API work: result at the beginning was single "Getting started" file, now result is 0. It seems that service account has no any relation to my own and delegating to doman has no meening for me as I have no domain account.
Are there any possibility to manage my simple google account google drive files by REST API ?
I always get this error when trying to add files under a folder in google drive, the folder is marked shared with mode "anyone in abc.com can view and edit"
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_grant"
}
I followed this procedure from Google Developers Guide
Delegating domain-wide authority to the service account
If your application accesses user data, the service account that you created needs to be granted access to the Google Apps domain’s user data that you want to access.
The following steps must be performed by an administrator of the Google Apps domain:
Go to your Google Apps domain’s Admin console.
Select Security from the list of controls. If you don't see Security listed, select More controls from the gray bar at the bottom of the page, then select Security from the list of controls. If you can't see the controls, make sure you're signed in as an administrator for the domain.
Select Show more and then Advanced settings from the list of options.
Select Manage API client access in the Authentication section.
In the Client Name field enter the service account's Client ID.
In the One or More API Scopes field enter the list of scopes that your application should be granted access to. For example, if your application needs domain-wide access to the Google Drive API and the Google Calendar API, enter: https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/calendar.
Click Authorize.
Your application now has the authority to make API calls as users in your domain (to "impersonate" users). When you prepare to make authorized API calls, you specify the user to impersonate.
added the keys in application.properties
#Google Drive
google.drive.service.account=AAAAAAAAAAAA-i8jfnhhug5uoo9rm8ek2sf06b9452vb9#developer.gserviceaccount.com
google.drive.impersonate.account=my.user#abc.com
google.drive.pkey.file=abcdefgh-0aafb873fcc9.p12
google.drive.application.name=myapp-web
And then created a configuration class for drive
#Configuration
public class GoogleDriveAPIConfiguration {
#Resource
private Environment environment;
private final org.springframework.core.io.Resource pkeyFile = new ClassPathResource("abcdefgh-0aafb873fcc9.p12");
private static final String GOOGLE_RIVE_SERVICE_ACCOUNT = "google.drive.service.account";
private static final String GOOGLE_DRIVE_APPLICATION_NAME = "google.drive.application.name";
private static final String GOOGLE_DRIVE_IMPERSONATE_ACCOUNT = "google.drive.impersonate.account";
#Bean
#Autowired
public GoogleCredential googleCredential(JsonFactory jsonFactory, HttpTransport httpTransport)
throws GeneralSecurityException, IOException {
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(jsonFactory).setServiceAccountId(environment.getProperty(GOOGLE_RIVE_SERVICE_ACCOUNT))
.setServiceAccountPrivateKeyFromP12File(getPrivateKeyFile())
.setServiceAccountScopes(Collections.singleton(DriveScopes.DRIVE))
.setServiceAccountId(environment.getProperty(GOOGLE_DRIVE_IMPERSONATE_ACCOUNT))
.build();
return credential;
}
#Bean
public JsonFactory getJsonFactory() {
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
return JSON_FACTORY;
}
#Bean
public HttpTransport getHttpTransport() throws GeneralSecurityException, IOException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
return httpTransport;
}
#Bean
#Autowired
public Drive getDrive(HttpTransport httpTransport, JsonFactory JSON_FACTORY, GoogleCredential credential) {
Drive drive = new Drive.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(
GOOGLE_DRIVE_APPLICATION_NAME).build();
return drive;
}
private File getPrivateKeyFile() throws IOException {
return pkeyFile.getFile();
}
}
The app starts up without any error, here's file upload code (full file in this gist)
#Autowired
public void setDrive(Drive drive) {
this.drive = drive;
}
#Async
public void uploadResume(UploadData data, byte[] uploadFile) throws GeneralSecurityException, IOException {
LOGGER.info("Uploading file type {} of size {} to Drive",data.getContentType(),uploadFile.length);
com.google.api.services.drive.model.File file = new com.google.api.services.drive.model.File();
List<ParentReference> referneces = new ArrayList<ParentReference>();
referneces.add(buildParentReference(data.getApplication().getRole()));
file.setTitle(data.getFileName());
file.setParents(referneces);
file.setCopyable(true);
file.setEditable(true);
file.setWritersCanShare(true);
file.setTitle(data.getFileName());
ByteArrayInputStream inputBytes = new ByteArrayInputStream(uploadFile);
InputStreamContent fileUpload = new InputStreamContent(data.getContentType(), new BufferedInputStream(inputBytes));
fileUpload.setLength(uploadFile.length);
Drive.Files.Insert request = drive.files().insert(file, fileUpload);
request.getMediaHttpUploader().setProgressListener(getProgressListener());
request.execute();
}
The error message really doesn't help too, any ideas where I am going wrong?
How do I get the refresh token and the access token from the first time authorization code? And, how do I reuse this refresh token to get a new access token to upload to Google Drive using the Java API? This is not a web application. It's in Java Swing code.
We can reuse the refresh token to get new access token by following code
public class OAuthRefreshToken implements CredentialRefreshListener {
public static GoogleCredential getAccessTokenFromRefreshToken( String refreshToken, HttpTransport transport, com.google.api.client.json.JsonFactory jsonFactory, String CLIENT_ID, String CLIENT_SECRET) throws IOException
{
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder()
.setTransport(transport).setJsonFactory(jsonFactory)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET);
credentialBuilder.addRefreshListener(new OAuthRefreshToken());
GoogleCredential credential = credentialBuilder.build();
credential.setRefreshToken(refreshToken);
credential.refreshToken();
return credential;
}
#Override
public void onTokenErrorResponse(Credential arg0, TokenErrorResponse arg1)
throws IOException {
// TODO Auto-generated method stub
System.out.println("Error occured !");
System.out.println(arg1.getError());
}
#Override
public void onTokenResponse(Credential arg0, TokenResponse arg1)
throws IOException {
// TODO Auto-generated method stub
System.out.println(arg0.getAccessToken());
System.out.println(arg0.getRefreshToken());
}
}
Here is a solution I recently made up from the basic example in the Google Drive docs and some experimenting:
The IApiKey contains the static strings CLIENT_ID, and so on. ITokenPersistence is an interface which allows to load and save a token (as String). It decouples the persistence mechanism (I used Preferences for an Eclipse e4 RCP application) from the Uploader. This can be as simple as storing the token in a file The IAthorizationManager is an interface which is is used to let the user grant acces and enter the code to create the refresh token. I implemented a Dialog containing a browser widget to grant access and a text box to copy and paste the code to. The custom exception GoogleDriveException hides the API classes from the rest of the code.
public final class Uploader implements IApiKey {
public static final String TEXT_PLAIN = "text/plain";
private final ITokenPersistence tokenManager;
private final IAuthorizationManager auth;
public Uploader(final ITokenPersistence tm, final IAuthorizationManager am) {
this.tokenManager = tm;
this.auth = am;
}
private GoogleCredential createCredentialWithRefreshToken(
final HttpTransport transport,
final JsonFactory jsonFactory,
final String clientId,
final String clientSecret,
final TokenResponse tokenResponse) {
return new GoogleCredential.Builder().setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(clientId, clientSecret)
.build()
.setFromTokenResponse(tokenResponse);
}
/**
* Upload the given file to Google Drive.
* <P>
* The name in Google Drive will be the same as the file name.
* #param fileContent a file of type text/plain
* #param description a description for the file in Google Drive
* #return Answer the ID of the uploaded file in Google Drive.
* Answer <code>null</code> if the upload failed.
* #throws IOException
* #throws {#link GoogleDriveException} when a <code>TokenResponseException</code> had been
* intercepted while inserting (uploading) the file.
*/
public String upload(final java.io.File fileContent, final String description) throws IOException, GoogleDriveException {
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
// If we do not already have a refresh token a flow is created to get a refresh token.
// To get the token the user has to visit a web site and enter the code afterwards
// The refresh token is saved and may be reused.
final GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport,
jsonFactory,
CLIENT_ID,
CLIENT_SECRET,
Arrays.asList(DriveScopes.DRIVE))
.setAccessType("offline")
.setApprovalPrompt("auto").build();
final String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
final String refreshToken = this.tokenManager.loadRefreshToken();
GoogleCredential credential = null;
if( refreshToken == null ) {
// no token available: get one
String code = this.auth.authorize(url);
GoogleTokenResponse response = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
this.tokenManager.saveRefreshToken(response.getRefreshToken());
credential = this.createCredentialWithRefreshToken(httpTransport, jsonFactory, CLIENT_ID, CLIENT_SECRET, response);
}
else {
// we have a token, if it is expired or revoked by the user the service call (see below) may fail
credential = new GoogleCredential.Builder()
.setJsonFactory(jsonFactory)
.setTransport(httpTransport)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build();
credential.setRefreshToken(refreshToken);
}
//Create a new authorized API client
final Drive service = new Drive.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(APP_NAME)
.build();
//Insert a file
final File body = new File();
body.setTitle(fileContent.getName());
body.setDescription(description);
body.setMimeType(TEXT_PLAIN);
final FileContent mediaContent = new FileContent(TEXT_PLAIN, fileContent);
try {
final File file = service.files().insert(body, mediaContent).execute();
return ( file != null ) ? file.getId() : null;
} catch (TokenResponseException e) {
e.printStackTrace();
throw new GoogleDriveException(e.getDetails().getErrorDescription(), e.getCause());
}
}
}