I want to read Google Spreadsheets using Java, and the recommended way to do this is using the Google Spreadsheets API.
The problem begins when you want to make procedures secure, so they encourage you to use OAuth 2.0. In the official page they show how to do this using only .NET and say that "the Java client library doesn't currently support OAuth 2.0", and they give alternatives like using OAuth 1.0 or Client Login using directly email and password.
Is this for sure?, isn't there a way to do OAuth 2.0 Authentication through Java, maybe not using directly the Java client library, but through requests with specific parameters.
Please I would appreciate any suggestions.
I also found it quite silly that the developer docs provided Java examples for everything except OAuth2. Here's some sample code that I used to get it working. For completeness it includes the retrieving spreadsheets example in the later section. Note also that you have to add the required scopes to the Java DrEdit example as shown below.
public class GSpreadsheets {
private static final String CLIENT_ID = "YOUR_CLIENT_ID";
private static final String CLIENT_SECRET = "YOUR_SECRET_ID";
private static final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
public static void main(String[] args) throws Exception {
if (CLIENT_ID.equals("YOUR_CLIENT_ID") || CLIENT_SECRET.equals("YOUR_SECRET_ID")) {
throw new RuntimeException(
"TODO: Get client ID and SECRET from https://cloud.google.com/console");
}
// get credentials similar to Java DrEdit example
// https://developers.google.com/drive/examples/java
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, CLIENT_ID, CLIENT_SECRET,
Arrays.asList(DriveScopes.DRIVE,
"https://spreadsheets.google.com/feeds",
"https://docs.google.com/feeds"))
.setAccessType("online")
.setApprovalPrompt("auto").build();
String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
System.out.println("Please open the following URL in your "
+ "browser then type the authorization code:");
System.out.println(" " + url);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String code = br.readLine();
GoogleTokenResponse response = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
GoogleCredential credential = new GoogleCredential().setFromTokenResponse(response);
// create the service and pass it the credentials you created earlier
SpreadsheetService service = new SpreadsheetService("MyAppNameHere");
service.setOAuth2Credentials(credential);
// Define the URL to request. This should never change.
URL SPREADSHEET_FEED_URL = new URL(
"https://spreadsheets.google.com/feeds/spreadsheets/private/full");
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.getFeed(SPREADSHEET_FEED_URL, SpreadsheetFeed.class);
List<SpreadsheetEntry> spreadsheets = feed.getEntries();
// Iterate through all of the spreadsheets returned
for (SpreadsheetEntry spreadsheet : spreadsheets) {
// Print the title of this spreadsheet to the screen
System.out.println(spreadsheet.getTitle().getPlainText());
}
}
}
The Google Data Java Client Library now supports OAuth 2.0:
https://code.google.com/p/gdata-java-client/source/detail?r=505
Unfortunately, there are no complete samples in the library showing how to use it. I'd recommend checking these two links to put together the information to make it work:
https://code.google.com/p/google-oauth-java-client/wiki/OAuth2
https://code.google.com/p/google-api-java-client/wiki/OAuth2
[Edit]
Java OAuth2 code
Blog post on [google-spreadsheet-api] and OAuth2, with code
http://soatutorials.blogspot.co.at/2013/08/google-spreadsheet-api-connecting-with.html
Related question: OAuth2 authorization from Java/Scala using google gdata client API
[end edit]
I used: Google drive DrEdit tutorial, full example shows how to use OAuth 2.0 with Drive. The code works with google spreadsheets GData style API. (note: does not include refresh token, but the refresh token works as you would expect, so not hard too add.) -
Extra Note: A better documented API is Google-Apps-Script.
Related
I have made a program in Java that generates a spreadsheet filled with statistics for VEX teams. My whole goal is to essentially have a program be able to generate a spreadsheet and change that sheet's ownership to a specific email. The Sheets functionality works completely, being able to create and modify spreadsheet values easily(using the Sheets API). The problem is when I try to change the ownership of the file using the Drive API, I get an "Insufficient Permission" message when running Drive.permissions().create.
When using the APIs explorer and testing it out myself, I was able to transfer ownership of spreadsheets, but I cannot do it within Java.
Here is how I create my GoogleCredential:
private void setCredential() throws GeneralSecurityException, IOException {
//Create new transport
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
//Build authenticated credential
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setClientSecrets(Constants.GOOGLE_CLIENT_ID,
Constants.GOOGLE_CLIENT_SECRET)
.build();
credential.setAccessToken(this.accessToken)
.setRefreshToken(Constants.GOOGLE_REFRESH_TOKEN);
//Set class field
this.credential = credential;
}
I noticed that I do not have any actual scopes here(which is weird, since this credential still works when the Sheets API does its work), in which I have looked around on how to input a proper scope here but found nothing. I don't know if this is why my program errors, or if it is for another reason.
This is how I call the method to transfer ownership:
private void transferOwnership(Drive driveService) throws IOException {
//Print message
System.out.printf("Transferring ownership to %s", this.usrEmail);
//Build request body
Permission body = new Permission()
.setRole("owner")
.setType("user")
.setEmailAddress(this.usrEmail);
//Execute Drive request
Permission permission = driveService.permissions().create(this.spreadsheetId, body)
.setFileId(this.spreadsheetId)
.setEmailMessage("Test - Replace with something")
.setSendNotificationEmail(true)
.setSupportsTeamDrives(true)
.setTransferOwnership(true)
.setUseDomainAdminAccess(false)
.setFields("emailAddress")
.execute();
//Print message
System.out.printf("Ownership successfully transferred to %s", this.usrEmail);
}
These are my questions:
Am I building my credential wrong? If so, how should I properly build it?
Do I need to make two different credentials?(one for Sheets API, one for Drive API)
How does my credential work fine with the Sheets API even though a scope isn't defined, but it does not work with the Drive API
EDIT: The way I use the APIs explorer to change ownership
EDIT 2: This is how I get my access token. As for the refresh token, I use the Oauth Playground from google.
public void setAccessToken() throws IOException, GeneralSecurityException{
//Create a token response using refresh token and oauth credentials
this.token_response = new GoogleRefreshTokenRequest(GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(),
Constants.GOOGLE_REFRESH_TOKEN, Constants.GOOGLE_CLIENT_ID, Constants.GOOGLE_CLIENT_SECRET)
.execute();
//Set the access token
this.accessToken = token_response.getAccessToken();
}
I am currently trying to simply delete a row off my Google Spreadsheet using the Google Sheets v4 API.
Here is the code I am using:
private void deleteRow()
{
List<Request> requests = new ArrayList<>();
DeleteDimensionRequest deleteDimensionRequest = new DeleteDimensionRequest();
DimensionRange dimensionRange = new DimensionRange();
dimensionRange.setStartIndex(14);
dimensionRange.setEndIndex(15);
deleteDimensionRequest.setRange(dimensionRange);
requests.add(new Request()
.setDeleteDimension(deleteDimensionRequest)
);
BatchUpdateSpreadsheetRequest batchUpdateRequest = new BatchUpdateSpreadsheetRequest()
.setRequests(requests);
try
{
mService.spreadsheets().batchUpdate("Spreadsheet_ID", batchUpdateRequest).execute();
}
catch(IOException e)
{
e.printStackTrace();
}
}
The error this function gives me is:
08-14 15:47:10.818 26956-27285/com.xxx.xxxxx.xxxxxxxx
W/GoogleAuthUtil: isUserRecoverableError status: NEED_PERMISSION
In my other class file, I've already indicated the scopes of permissions including drive and spreadsheets.
Here is the picture of the error:
In the java quickstart...
public static Credential authorize() throws IOException {
// Load client secrets.
InputStream in =
SheetsQuickstart.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;
}
Is this the sort of oath credential in addition to the one provided by the android quickstart that I need to include?
Based from the given JSON response, encountered error is due to insufficient authentication scope. You can try to check required OAuth 2.0 scope information for the Google Sheets API as given in Authorizing requests with OAuth 2.0.
Please note that requests to the Google Sheets API for non-public user data must be authorized by an authenticated user. Likewise, if an application needs to create spreadsheets, or otherwise manipulate their metadata, then the application must also request a Google Drive API scope.
Additionally, the solution given in this SO post - 403 Forbidden error when accessing Google Drive API downloadURL regarding error code 403 might also help.
The problem is likely that you are loaded stored credentials with the insufficient scopes, and you need to delete your stored credentials and authorize access again.
You code appears to be adapted from the Java quickstart, which requests a readonly scope. Trying to use that scope for a write operation would fail with this error.
Since there is not one single question relating to managing sheets with an android application, here is the way to request an additional OATH 2.0 from scratch, which took me a few hours to understand:
Strategy to get the right additional authorization...
https://developers.google.com/identity/protocols/OAuth2InstalledApp#libraries
Diagram:
All of these requests can be handled with some imports such as HttpUrlConnection... within AsyncTask.
The list of scopes...
https://developers.google.com/sheets/guides/authorizing
I am trying to write a Google App Engine app, which accesses a spreadsheet from Google Drive by using the Google Sheets API. I have found an example from Google, which shows how to access the Calendar from Google app engine application: https://github.com/google/google-api-java-client-samples/tree/master/calendar-appengine-sample
I took that as a base and try to replace the Calendar call with the SpreadSheet call. In this example, the Calendar data is accessed by this code:
static Calendar loadCalendarClient() throws IOException {
String userId = UserServiceFactory.getUserService().getCurrentUser().getUserId();
Credential credential = newFlow().loadCredential(userId);
return new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).build();
}
However, Sheets API does not provide any Sheets.Builder. Instead, from another example (https://developers.google.com/google-apps/spreadsheets/), I found that they use the following code:
SpreadsheetService service = new SpreadsheetService("MySpreadsheetIntegration-v1");
// TODO: Authorize the service object for a specific user (see other sections)
So, the trick is to authorize the Sheets service with the credentials. I'm trying to merge these examples, but without success:
String userId = UserServiceFactory.getUserService().getCurrentUser().getUserId();
Credential credential = flow.loadCredential(userId); // the flow object which was used to create an OAuth flow
SpreadsheetService service = new SpreadsheetService("AppName");
credential.refreshToken();
service.setOAuth2Credentials(credential);
URL SPREADSHEET_FEED_URL = new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");
SpreadsheetFeed feed = service.getFeed(SPREADSHEET_FEED_URL, SpreadsheetFeed.class);
After calling the service.getFeed method I receive an exception:
com.google.gdata.client.GoogleService$SessionExpiredException: OK
Token invalid - AuthSub token has wrong scope</TITLE>
Token invalid - AuthSub token has wrong scope</H1>
Error 401
at com.google.gdata.client.http.GoogleGDataRequest.handleErrorResponse(GoogleGDataRequest.java:570)
at com.google.gdata.client.http.HttpGDataRequest.checkResponse(HttpGDataRequest.java:560)
at com.google.gdata.client.http.HttpGDataRequest.execute(HttpGDataRequest.java:538)
at com.google.gdata.client.http.GoogleGDataRequest.execute(GoogleGDataRequest.java:536)
at com.google.gdata.client.Service.getFeed(Service.java:1135)
Any ideas how to fix that?
I am trying to use a URL shortener, with Scribe, based on this example.
However, I want to make sure I can track the visits which my short URL gets, which means it must be unique. To create unique links, I need to authenticate with Google, as per this.
Signed in
Your links are automatically added to goo.gl where you can track their use.
A unique short URL is created each time a long URL is shortened.
Signed out
Your links won’t show up on your goo.gl page.
The same short URL is reused each time a long URL is shortened by you or someone else.
In the example, the oAthRequest is not signed with the oAuthService. I have updated this so that it can sign the request and send it (as a signed in user).
Here is my code:
private static final String API_KEY = "XXXXXXXX";
private static final String API_URL = "https://www.googleapis.com/urlshortener/v1/url";
private static final String API_URL_WITH_KEY = API_URL + "?key=" + API_KEY;
public TrackableLink createTrackableLink(String longUrl) {
OAuthService oAuthService = new ServiceBuilder()
//Google Api Provider - Google's URL Shortener API is part of Google Platform APIs
.provider(GoogleApi.class)
/*
Using "anonymous" as API Key & Secret because Google's URL Shortener service
does not necessarily requires App identification and/or User Information Access
*/
.apiKey("anonymous")
.apiSecret("anyonymous")
//OAuth 2.0 scope for the Google URL Shortener API
.scope("https://www.googleapis.com/auth/urlshortener")
//build it!
.build();
Token requestToken = oAuthService.getRequestToken();
OAuthRequest request = new OAuthRequest(Verb.POST, API_URL_WITH_KEY);
request.addHeader("Content-Type", "application/json");
request.addPayload(new JSONObject().put(RESPONSE_LONG_URL, longUrl)
.toString());
oAuthService.signRequest(requestToken, request);
Response response = request.send();
JSONObject json = new JSONObject(response.getBody());
String shortUrl = json.getString(RESPONSE_SHORT_URL);
TrackableLink tl = new TrackableLink(longUrl, shortUrl);
return tl;
}
I replace the "anonymous" details with my values from the Google API website, and I get this exception:
Can't extract token and secret from this: 'Consumer is not registered: 7629638329XXXXXXXXXXX.apps.googleusercontent.com
I'm not sure exactly what I am doing wrong here. I have tried almost every combination of values for the key/secret from the various keys the google console gives me, could this be caused by something else other than something up with the API key?
Any ideas why I am getting the consumer is not registered error? On my Google account, I have enabled the API.
I have registered my web application for use with oath2 using the following instructions:
http://code.google.com/apis/accounts/docs/OAuth2.html
This means my client is created with a client ID, client secret and Redirect URI.
Once I have configured my web application as per
http://code.google.com/apis/accounts/docs/OAuth2WebServer.html
I recieve a code in a request parameter from google, which I can then use to request an access token, which comes in a JSON in a format along the lines of:
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer"
}
Once this is done, I can use that access token to access a google api on behalf of the user:
GET https://www.googleapis.com/oauth2/v1/userinfo?access_token=1/fFBGRNJru1FQd44AzqT3Zg
This as documented is done by simply passing the access token as a request parameter.
However when I move onto using a Java API (In this case google contacts) I get the following in the documentation for HMAC-SHA1:
GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
oauthParameters.setOAuthConsumerKey(CONSUMER_KEY);
oauthParameters.setOAuthConsumerSecret(CONSUMER_SECRET);
oauthParameters.setOAuthToken(ACCESS_TOKEN);
oauthParameters.setOAuthTokenSecret(TOKEN_SECRET);
DocsService client = new DocsService("yourCompany-YourAppName-v1");
client.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());
URL feedUrl = new URL("https://docs.google.com/feeds/default/private/full");
DocumentListFeed resultFeed = client.getFeed(feedUrl, DocumentListFeed.class);
for (DocumentListEntry entry : resultFeed.getEntries()) {
System.out.println(entry.getTitle().getPlainText());
}
Or the following for RSA-SHA1
GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
oauthParameters.setOAuthConsumerKey(CONSUMER_KEY);
oauthParameters.setOAuthConsumerSecret(CONSUMER_SECRET);
oauthParameters.setOAuthToken(ACCESS_TOKEN);
PrivateKey privKey = getPrivateKey("/path/to/your/rsakey.pk8"); // See above for the defintion of getPrivateKey()
DocsService client = new DocsService("yourCompany-YourAppName-v1");
client.setOAuthCredentials(oauthParameters, new OAuthRsaSha1Signer(privKey));
URL feedUrl = new URL("https://docs.google.com/feeds/default/private/full");
DocumentListFeed resultFeed = client.getFeed(feedUrl, DocumentListFeed.class);
for (DocumentListEntry entry : resultFeed.getEntries()) {
System.out.println(entry.getTitle().getPlainText());
}
First off, it seems that if I was doing standard http rather than the java wrapper, all I would need to provide is an access token. Am I missing something or where have these additional parameters come from? Mainly TOKEN_SECRET, which there is no mention of in the docunentation. There is also no mention of having to provide CONSUMER_KEY and CONSUMER_SECRET. I am presuming they are alternative names for client id and client secret, but I do not understand why I am now having to provide them. Finally when setting up my application using the google API's console, there was no mention whatsoever of the two different encryption types, and which one I am going to be using, am I missing something here aswell?
The Java code examples you show are based on OAuth 1.0 (not OAuth 2.0) which has some crypto requirements which were simplified in OAuth 2.0. In some cases with the Google Contacts API you need OAuth 1.0. See: http://code.google.com/apis/contacts/docs/3.0/developers_guide.html#GettingStarted