GAE backend / task queue - how to pass user credentials in parameter? - java

In our GAE application we process user`s documents in google drive and as this process takes sometimes more then 30seconds we are getting deadline exception as it is GAE frontend instance.
we would like to use backend instance. Problem is how to pass credentials (com.google.api.client.auth.oauth2.Credentials) to be able to initialize Google drive API.
How to pass user credentials into GAE task queue and then to backend instance to be able to use them later when task will be running ?
com.google.api.client.auth.oauth2.Credentials is not serializable ...
Is there any way ?

You'll need to keep the inputs to the credential so you can re-create it in the backend handler.
This is either an authorization code, or the access token and refresh token that you got in exchange for the authorization code. They're all strings so should serialize readily.
If all that sounds unfamiliar, I'd be curious to understand how you got the Credential in the first place. Links to useful documentation below:
Exchange the authorization code for a refresh and access token
Retrieve and Use OAuth 2.0 Credentials
Notably, sample code in that last link specifically includes a method that you're expected to implement to squirrel away the access/refresh tokens:
/**
* Retrieved stored credentials for the provided user ID.
*
* #param userId User's ID.
* #return Stored Credential if found, {#code null} otherwise.
*/
static Credential getStoredCredentials(String userId) {
// TODO: Implement this method to work with your database. Instantiate a new
// Credential instance with stored accessToken and refreshToken.
throw new UnsupportedOperationException();
}

I'd suggest using AppEngineCredentialStore to store the access and refresh tokens. Please take a look at calendar-appengine-sample as an example usage. Here's an example code snippet from Utils.java:
static GoogleAuthorizationCodeFlow newFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
getClientCredential(),
Collections.singleton(CalendarScopes.CALENDAR))
.setCredentialStore(new AppEngineCredentialStore())
.setAccessType("offline")
.build();
}
NOTE: I'm an owner of the google-api-java-client project.

Related

How to identify the authorised user based on Google calendar's callback

After user granted/allowed to access their calendar, google auth is responding with the callback URL with code.
Let's say two users are trying to give access to their calendar at the same time, the server will receive two callback request from google with code. In this case, how to identify the user from whom this callback is received?
I need to store the access_token and refresh_token along with the user email to synchronize the calendar events.
Any ideas?
There are a few ways to do this.
One is, you might ask the user to authorize sharing his or her email address along with calendar information. That solution is discussed here: Google OAuth API to get user's email address?.
Another is, you could directly validate the access_token and get the user ID that way. You could invoke the https://developers.google.com/apis-explorer/#search/oauth2/oauth2/v2/oauth2.tokeninfo endpoint. I am not sure whether this user ID is an E-mail address; it may be an opaque ID that they use to represent the user. Depending on your needs (are you just looking for a unique identifier?) that could work for you as well.
Finally, you can always retrieve the user's primary calendar using the primary keyword, and the id of that calendar will be the user's E-mail address. That's certainly a hack, but might unblock you for now.
Below is the code snippet used
public void authoriseCode(String code) throws IOException {
TokenResponse tokenResponse = flow.newTokenRequest(code).setRedirectUri(redirectURI).execute();
Credential credential = new GoogleCredential().setAccessToken(tokenResponse.getAccessToken());
Calendar calendar =
new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
String email = calendar.calendars().get("primary").execute().getId();
...
}
Another best way is to use the state parameter. We can pass state while submitting the request and in response, API will contain the state. Which would be session id or unique id to identity the actual request.

IllegalArgumentException When Accessing Google Sheets API

I'm attempting to read/write to Google Sheet (owned by me) via Google Sheets API v4. To get Credential object I'm using the following code:
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStream in = ResourceX.class.getResourceAsStream(CLIENT_SECRET_DIR);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
System.out.println(clientSecrets.toPrettyString());
// When I print this ^^ it loads contents of secret key exactly as expected
// Build flow and trigger user authorization request.
Builder builder = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES); // error occurs on this line
builder.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(CREDENTIALS_FOLDER)));
GoogleAuthorizationCodeFlow flow = builder.setAccessType("offline").build();
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}
I understand that IllegalArgumentException means that somehow the argument clientSecrets is illegal to be passed as parameter however when I print out the contents of json file (see print statement in code above) the contents seems to match the expected contents (I cannot print the contents on SO for security reasons but nothing was changed in the file since I downloaded it from Google).
I found a similar question here but there are no answers addressing the core question.
What possibly have I missed that may cause this exception?
Stacktrace:
Exception in thread "main" java.lang.IllegalArgumentException
at com.google.api.client.repackaged.com.google.common.base.Preconditions.checkArgument(Preconditions.java:111)
at com.google.api.client.util.Preconditions.checkArgument(Preconditions.java:37)
at com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets.getDetails(GoogleClientSecrets.java:82)
at com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow$Builder.<init>(GoogleAuthorizationCodeFlow.java:195)
at global_projects.GoogleSheetsAPI.getCredentials(GoogleSheetsAPI.java:100)
at global_projects.GoogleSheetsAPI.updateSheetByOauth2(GoogleSheetsAPI.java:116)
at amz.Main.main(Main.java:78)
Update:
I think I found the problem. clientSecrets expects a json file from OAuth 2.0 client ID not a Service account key/jsonfile (as described in this link). However, since I am accessing my own spreadsheet is there a way to access it using a service account key/json file or must I use OAuth which would require me to provide consent to edit my own spreadsheet?
There are 2 ways of accessing your Google sheets file using the Google Client API.
Create service account credentials for which you must give edit access in your particular sheet to the email which Google assigned for the account.
OR
Create Oauth2 client id credentials to use with generating a credential object. This method will prompt you, by way of return URL, to provide authorisation in browser. Once you approve, you can query for a refresh token that enables you to refresh your access token without needing the browser flow approval again.
You are using method (1) and you're getting the IllegalArgumenException error because you have not given access to the email assigned to the service account.

Google Play Games: tokenResponse includes access token, but no idToken

I'm currently working with Google's Games API. The client sends through a user's authorization code, alongside their GPlay ID.
I'm sending this off to validate with Google, with;
var tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
client_id,
client_secret,
idTokenString,
"")
.execute()
Where the client_id and client_secret are retrieved from our client_secret as retrieved from Google, and the idTokenString is the authorization code as provided by the user logging in to the client (format: 4/xxxxx..).
After retrieving the tokenResponse, the following will return the access token without issue;
var accessToken = tokenResponse.getAccessToken()
However, the idToken as retrieved from:
var idToken = tokenResponse.getIdToken()
returns with null. As such, attempting to get the user's data to validate they're the legitimate owner of the account with;
var idToken = tokenResponse.parseIdToken()
will return a nullpointer exception.
From googling on the topic, some users seem to think that the parseIdToken method is no longer in use, and that only the accessToken can be used to retrieve such information.
However, any solutions I've found have all required use of the getIdToken, which is also returning with null.
Does anyone have any ideas on what I may be doing wrong here, or if there's another expected method for retrieving the user's details after login?
It might be because getIdToken() is in BETA.
As stated in the documentation - Google API Client Library for Java:
Features marked with #Beta at the class or method level are subject to change. They might be modified or removed in any major release. Do not use beta features if your code is a library itself (that is, if your code is used on the CLASSPATH of users outside your control).
You might want to use getIdToken from GoogleSignInAccount as a workaround.
You can check the release notes for any update.
Hope this helps.

(Android/Java) Google Sheets v4 isUserRecoverableError status: NEED_PERMISSION

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

Upload videos to Youtube from my web server in Java

My goal is to upload videos that are uploaded to my web server to Youtube on my own channel, not the users' Youtube account (my web server is acting as a proxy).
I found the sample code for uploading video to Youtube here with the credential acquired this way. The problem that I have with this sample is that it writes to disk the credential, and it opens an http server. Since my web server can potentially have a lot of users uploading their videos concurrently, the credential file location has to be dynamic, and multiple binding to the same http port is not possible. Further more, after searching through other writing about uploading to Youtube, I think this approach is for users uploading to their Youtube account.
Could you share your experiences/code sample/solutions for my scenario? In short I am just trying to automate the process of me opening up Youtube dashboard, and uploading videos to a channel in my Youtube.
In general, starting at API V3, Google prefers OAuth2 over other mechanism, and uploading a video (or any other action that modifies user data) requires OAuth2.
Fortunately, there is a special kind of token called refresh token to the rescue. Refresh token does not expire like normal access token, and is used to generate normal access token when needed. So, I divided my application into 2 parts:
The 1st part is for generating refresh token, which is a Java desktop app, meant to be run by a user on a computer. See here for sample code from Google.
The 2nd part is is part of my web application, which uses a given refresh token to create a credential object.
Here is my implementation in Scala, which you can adapt to Java version easily:
For generating a refresh token, you should set the accessType to offline for the authorization flow. Note: if a token already exists on your system, it won't try to get new token, even if it does not have refresh token, so you also have to set approval prompt to force:
def authorize(dataStoreName: String, clientId: String, clientSecret: String): Credential = {
val builder = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
clientId,
clientSecret,
Seq(YouTubeScopes.YOUTUBE_UPLOAD)
)
val CREDENTIAL_DIRECTORY = s"${System.getProperty("user.home")}/.oauth-credentials"
val fileDataStoreFactory = new FileDataStoreFactory(new java.io.File(CREDENTIAL_DIRECTORY))
val dataStore: DataStore[StoredCredential] = fileDataStoreFactory.getDataStore(dataStoreName)
builder.setCredentialDataStore(dataStore).setAccessType("offline").setApprovalPrompt("force")
val flow = builder.build()
val localReceiver = new LocalServerReceiver.Builder().setPort(8000).build()
new AuthorizationCodeInstalledApp(flow, localReceiver).authorize("user")
}
val credential = authorize(dataStore, clientId, clientSecret)
val refreshToken = credential.getRefreshToken
For using the refresh token on the server, you can build a credential from a refresh token:
def getCredential = new GoogleCredential.Builder()
.setJsonFactory(JSON_FACTORY)
.setTransport(HTTP_TRANSPORT)
.setClientSecrets(clientId, clientSecret)
.build()
.setRefreshToken(refreshToken)
I have have bypassed the whole AuthorizationCodeInstalledApp authorize() method and created a new subclass which bypasses the jetty server implementation process.
The methods are as follows
getAuthorizationFromStorage : Get access token from stored credentials.
getAuthorizationFromGoogle : Get the authentication with the credentials from Google creates the url that will lead the user to the authentication page and creating a custom defined name-value pair in the state parameter. The value should be encoded with base64 encoder so we can receive the same code redirected from google after authentication.
saveAuthorizationFromGoogle : Save the credentials that we get from google.
Create the GoogleAuthorizationCodeFlow object from the credentialDatastorfrom the response received from the google after authentication.
Hit google to get the permanent refresh-token that can be used to get the accesstoken of the user any time .
Store the tokens like accesstoken and refreshtoken in the filename as userid
Checkout the code Implementation here

Categories