Google Drive API through Google App Engine - java

I'm trying to use the Google Drive API through the App Identity interface provided with Google App Engine. This basically allows my web application to communicate with Google's APIs from server to server.
I don't need my users to login, I simply need to display my own Google Drive documents.
However, after I set all the appropriate values and scopes, and enable all the right Google Drive knobs on the console page, I still get this for a simple GET request to https://www.googleapis.com/drive/v2/files:
{ "error": { "errors": [ { "domain": "usageLimits", "reason": "dailyLimitExceededUnreg", "message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.", "extendedHelp": "https://code.google.com/apis/console" } ], "code": 403, "message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup." }}
What's wrong? What am I missing? Here's the code that actually does the request - funny thing is that it works great if I use other APIs such as the URL shortener API:
var scopes = new java.util.ArrayList();
scopes.add("https://www.googleapis.com/auth/drive");
var appIdentity = AppIdentityServiceFactory.getAppIdentityService();
var accessToken = appIdentity.getAccessToken(scopes);
var url = new URL("https://www.googleapis.com/drive/v2/files");
var connection = url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("GET");
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Authorization", "OAuth " + accessToken.getAccessToken());
EDIT
If I simply change the API to use the urlshortner API for example, it works:
var url = new URL("https://www.googleapis.com/urlshortener/v1/url/history");
And output:
{ "kind": "urlshortener#urlHistory", "totalItems": 0, "itemsPerPage": 30}
So there must be something not working with Google Drive and App Identity?
EDIT 2
I've found some help from the correct answer here: https://stackoverflow.com/a/12526286/50394
But it's talking about setting Client API scopes on Google Apps, and I'm not using Google Apps, I'm simply using Google App Engine's domain foo.appspot.com

The 403 error you are getting means that there was no Authorization header in your GET. The logic is that without an Authorization header, you are anonymous (you are legion blah blah :-)). The Drive quota for anonymous use is zero, hence the message. URL shortener has a higher quota for anonymous so it works.
I suggest you change the URL to point to an http server of your own, and check what headers you are actually sending.

AFAICT you should be using Bearer in the Authorization header.
Probably what's happening is, Drive API doesn't recognize the service account (because of the wrong header?) and thus taking it as an anonymous request since no key parameter wasn't provided either (see common query params).
Try this:
connection.addRequestProperty("Authorization", "Bearer " + accessToken.getAccessToken());
Or you could try adding the token as access_token query param.

I think you should at least setup an API console entry with Drive API enabled at https://code.google.com/apis/console
Once you create this you'll get an ID you can use in your GoogleCredential object. From the GoogleCredential object you can get the access token which you can than add to your request.

What I read here (Google drive via service accounts) was that you use a slightly different style that uses an API KEY that you retrieve from the Developer Console.
The pertinent parts for me were to generate a "Key for Server Applications", then use this technique, which I hadn't read anywhere else!
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
AppIdentityCredential credential =
new AppIdentityCredential.Builder(DriveScopes.DRIVE).build();
// API_KEY is from the Google Console as a server API key
GoogleClientRequestInitializer keyInitializer =
new CommonGoogleClientRequestInitializer(API_KEY);
Drive service = new Drive.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential)
.setGoogleClientRequestInitializer(keyInitializer)
.build();

This answer claims that:
Service Accounts are not supported by the Drive SDK due to its
security model.
If that's still true, one workaround is to perform a regular OAuth dance once with a regular Google Account, and persist the access and refresh token in the datastore.

Related

400 Bad Request when refreshing token via Gmail Java API

Hi I'm trying to implement sending/receiving email using Google's gmail api on my server:
private GoogleCredential authorize(HttpTransport httpTransport, JsonFactory jsonFactory ) {
try{
Resource resource = new ClassPathResource("my_key_in_json_format.");
InputStream input = resource.getInputStream();
GoogleCredential credential = GoogleCredential.fromStream(input);
credential.createScoped(GmailScopes.all());
credential.refreshToken();
return credential;
}catch(IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
But I'm getting the following exception when the credential tries to refresh token:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_scope",
"error_description" : "Bad Request"
}
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:394)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:493)
at com.snobo.util.GmailService.authorize(GmailService.java:79)
I've tried changing the scope parameters to:
Collection<String> SCOPES = Collections.unmodifiableCollection(Arrays.asList(new String[]{GmailScopes.GMAIL_READONLY}));
And it also failed the same when refreshing token. Google's online document is not really Java friendly. Anyone run into similar issues?
I found the answer to my problem based on this thread after searching around:
400 Bad Request on Gmail API with php
"You should not be using a service account if you just want to access one account (your own). Service accounts are their own account and they're not Gmail accounts. They work well for APIs that don't need a user (e.g. maps, search) or when you are using a Google Apps for Work domain and want delegation enabled for all users in the domain (by domain admin, so you don't need individual user authorization)."
I have modified my implementation to use oauth web flow now. I'm really disappointed on Google's documentation as this matter should be addressed outright and as concise as possible. I'm sure "Service Account" and "domain wide delegation" mis-led many developers to use the Service Account approach for many types of personal/individual account application.

Bad request (invalid value) when trying to retrieve in-app purchase info using the Java Google Play Developer API v3 with a valid purchase token

When using the Java Google Play Developer API (version 3), and requesting purchase information for a valid purchase token, I get the below exception. The API call returns a 400 Bad Request response with the following message.
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Invalid Value",
"reason" : "invalid"
} ],
"message" : "Invalid Value"
}
Unfortunately there is no further information. I do not know what value is invalid.
First, we have contacted Google support about this. Unfortunately we have been going back and forth with Google support for almost 2 months and they still haven't even indicated they correctly understand what our problem is. Every time they reply, they give us suggestions that have literally nothing to do with the Google Play Developer API, even after thoroughly explaining the problem multiple times. This is the most frustrating support experience I've had in 20 years. Google support is clearly not going to be of any help here.
Second, we have successfully linked our Google Play account and our back-end. All necessary configuration is complete and working. On our back-end we are getting notifications from Google Play whenever an Android in-app purchase is made, and these notifications include a purchase token. This took some substantial effort, but I am confident this is now all correct, our service account is configured correctly, and all permissions and such are good.
I am able to successfully call the API that returns a list of available in-app-purchase products. I get a full and correct listing of the products we have configured in Google Play for users to purchase in our app. In order to make this call, we must provide credentials, and I assume the server is validating these credentials when we make this call. Since I can successfully make this call, I am assuming the "Invalid Value" is not related to our credentials. We are using a Google-generated JSON file created for a specific service account (not using a p12 file).
I have searched StackOverflow, and the rest of the internet looking for information on this issue. I have found many pages indicating the same or similar problem, but using an older version of the Google Play Developer API, which apparently did things differently. The solutions offered do not seem to apply to the v3 API. I have been searching for many weeks and trying various solutions I've come across all with no success.
I have written a test app that I run on my local PC which works when I am requesting a listing of in-app products, but does not work when requesting the purchase details for a given purchase token. I have tried with many different purchase tokens. I read that the test tokens are not actually valid for use with this particular API, so I actually released to production a build of our app that I am able to make a real, actual purchase with. I have done this, been charged by Google, and our back-end has received real and valid purchase tokens from these purchases.
private static void testGooglePlayDevAPI_Purchase() throws Throwable {
final String productIdSku = "[the_product_sku]";
final String purchaseToken = "[a_valid_purchase_token]";
final String packageName = "[our_app_id]";
final File GOOGLE_PLAY_DEV_API_CREDENTIALS =
new File("path_to_file/google-api-credentials.json");
final FileInputStream credentials =
new FileInputStream(GOOGLE_PLAY_DEV_API_CREDENTIALS);
final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
final HttpTransport TRANSPORT =
GoogleNetHttpTransport.newTrustedTransport();
String scope = AndroidPublisherScopes.ANDROIDPUBLISHER;
GoogleCredential credential = GoogleCredential
.fromStream(credentials, TRANSPORT, JSON_FACTORY)
.createScoped(Arrays.asList(scope));
AndroidPublisher publisher = new AndroidPublisher
.Builder(TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(packageName)
.build();
try {
AndroidPublisher.Purchases purchases = publisher.purchases();
AndroidPublisher.Purchases.Products products = purchases.products();
AndroidPublisher.Purchases.Products.Get get =
products.get(packageName, productIdSku, purchaseToken);
ProductPurchase product = get.execute();
System.out.println(String.format(
"Product purchase data found [developerPayload=%s]",
product.getDeveloperPayload()));
} catch (Throwable t) {
System.out.println(ExceptionSupport.getExceptionDetails(t));
}
}
I expect the above code to give me a response with valid product purchase data (a valid ProductPurchase instance that I can interrogate and process). Instead, all I ever get here is a 400 Bad Request response indicating that SOME VALUE (I don't know which) is invalid. The actual response is:
com.google.api.client.googleapis.json.GoogleJsonResponseException:
400 Bad Request
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Invalid Value",
"reason" : "invalid"
} ],
"message" : "Invalid Value"
}
UPDATE:
I've provided my own answer to this question below.
I'll provide my own answer here because we finally found a work-around. It turns out that something in the Java Google Play Developer API (v3) client library is not working correctly. I don't know what it is, but the documentation for that client library is terrible and Google provides NO examples of using it. However, it turns out that we can successfully use the HTTPS/JSON/OAuth2 API directly without going through the Java client. We have implemented our server-side solution using this approach and caching the OAuth2 tokens in memcache until they expire.
I double-checked and we were emailing back-and-forth with Google support for 2 months!!! They were of no assistance at all. I find this to be remarkable (in a bad way).

Firestore import/export per API

I'm searching for a way to call the firestore import/export functionality programmatically from java code.
What i found so far is that the nice firestore client library does not yet support the import/export calls. But the more low level rest/grpc api already supports them. Using the java library i tried the following:
Firestore firestoreApi = new Firestore
.Builder(UrlFetchTransport.getDefaultInstance(), new GsonFactory(), null)
.setApplicationName(SystemProperty.applicationId.get())
.build();
GoogleFirestoreAdminV1beta2ImportDocumentsRequest importRequest = new GoogleFirestoreAdminV1beta2ImportDocumentsRequest();
importRequest.setInputUriPrefix(String.format("gs://{}/{}/", BUCKET, image));
GoogleLongrunningOperation operation = firestoreApi
.projects()
.databases()
.importDocuments("projects/" + SystemProperty.applicationId.get() + "/databases/(default)", importRequest)
.execute();
Which sadly ends with missing permissions when run in app engine:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 401
{
"code": 401,
"errors": [
{
"domain": "global",
"location": "Authorization",
"locationType": "header",
"message": "Login Required.",
"reason": "required"
}
],
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
I cannot get the official way to login to work, because the firestore builder does not have a method to accept a instance of AppEngineCredentials.
I already checked the python client library which also seems not support these methods (yet).
Does anyone have a idea how i can either login with the old rest api or get a client library which supports these methods (some language which runs on app engine please :) )
Thanks for reading!
Carsten
You can adapt this Cloud Datastore example for Cloud Firestore. See how they get an access token here:
import com.google.appengine.api.appidentity.AppIdentityService;
import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
// Get an access token to authorize export request
ArrayList<String> scopes = new ArrayList<String>();
scopes.add("https://www.googleapis.com/auth/datastore");
final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
final AppIdentityService.GetAccessTokenResult accessToken =
AppIdentityServiceFactory.getAppIdentityService().getAccessToken(scopes);
connection.addRequestProperty("Authorization", "Bearer " + accessToken.getAccessToken());

Youtube Video id by using Service account - got error 403 response

I’m developing Java code to retrieve Video Id for the corresponding asset id by using service account authentication.I have followed below mentioned steps.
Step 1: A Java program is written to retrieve the access token from Google authentication using service account.
the code is for getting access_token:
String EmailId = "XXXXXXXXXXX#developer.gserviceaccount.com";
//passing Scope
#SuppressWarnings({ "unchecked", "rawtypes" })
List<String>scops = new <String>ArrayList();
scops.add("https://www.googleapis.com/auth/youtubepartner");
final HttpTransport TRANSPORT = new NetHttpTransport();
final JsonFactory JSON_FACTORY = new JacksonFactory();
// Create a listener for automatic refresh OAuthAccessToken
List<CredentialRefreshListener> list = new ArrayList<CredentialRefreshListener>();
list.add(new CredentialRefreshListener() {
#Override
public void onTokenResponse(Credential credential,
TokenResponse tokenResponse) throws IOException {
System.out.println(tokenResponse.toPrettyString());
}
public void onTokenErrorResponse(Credential credential,
TokenErrorResponse tokenErrorResponse)
throws IOException {
System.err.println("Error: "
+ tokenErrorResponse.toPrettyString());
}
});
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(EmailId)
.setServiceAccountScopes(scops)
.setServiceAccountPrivateKeyFromP12File(new File("test.p12"))
.setRefreshListeners(list)
.build();
credential.refreshToken();
Step 2: The access token that is obtained in step 1 is passed as an argument in the below mentioned URL to get Youtube video id
Sample url is:
https://www.googleapis.com/youtube/partner/v1/claimSearch?assetId=xxxxxxxxx&onBehalfOfContentOwner=xxxxxxxx&status=active&access_token=ya29.-gCmzBHciDghrj2EDtBn1Vx0MV38pNLZTvqfwOyG0hNJCj75nsCBA5zaxmP1sr7UqI7ZrYI3AIZstA
I’m getting 403 error code in step 2.
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API is not enabled for your project, or there is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}
],
"code": 403,
"message": "Access Not Configured. The API is not enabled for your project, or there is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your configuration."
}
}
already enabled “GOOGLE COULD JSON API” and “YOUTUBE DATA API”.
Could any one please help me to resolve this issue?
I know from working on a previous project that you need to enable the API in the Google console. I would try that first.
https://code.google.com/apis/console
You try to use Youtube's Content ID API endpoint /youtube/partner/v1/claimSearch.
This endpoint is not part of the Youtube Data API and you can only access it if you participate in the "Youtube Partner Program". Otherwise it is not even listed in the Developer's Console.
Further information here: https://developers.google.com/youtube/partner/
Note: The YouTube Content ID API is intended for use by YouTube content partners and is not accessible to all developers or to all YouTube users. If you do not see the YouTube Content ID API as one of the services listed in the Google Developers Console, see www.youtube.com/partner to learn more about the YouTube Partner Program.

2-legged OAuth with google-api-java-client

Does anyone know how to use 2-legged OAuth with google-api-java-client?
I'm trying to access the Google Apps Provisioning API to get the list of users for a particular domain.
The following does not work
HttpTransport transport = GoogleTransport.create();
GoogleHeaders headers = (GoogleHeaders) transport.defaultHeaders;
headers.setApplicationName(APPLICATION_NAME);
headers.gdataVersion = GDATA_VERSION;
OAuthHmacSigner signer = new OAuthHmacSigner();
signer.clientSharedSecret = CONSUMER_SECRET;
OAuthParameters oauthParameters = new OAuthParameters();
oauthParameters.version = OAUTH_VERSION;
oauthParameters.consumerKey = CONSUMER_KEY;
oauthParameters.signer = signer;
oauthParameters.signRequestsUsingAuthorizationHeader(transport);
I get the "com.google.api.client.http.HttpResponseException: 401 Unknown authorization header".
The header looks something like this
OAuth oauth_consumer_key="...", oauth_nonce="...", oauth_signature="...", oauth_signature_method="HMAC-SHA1", oauth_timestamp="...", oauth_version="1.0"
I also tried following without success
GoogleOAuthDomainWideDelegation delegation = new GoogleOAuthDomainWideDelegation();
delegation.requestorId = REQUESTOR_ID;
delegation.signRequests(transport, oauthParameters);
Any ideas?
Thanks in advance.
It seems that there was nothing wrong with the code. It actually works.
The problem was with the our Google Apps setup.
When you visit the "Manage OAuth key and secret for this domain" page (https://www.google.com/a/cpanel/YOUR-DOMAIN/SetupOAuth),
and enable "Two-legged OAuth access control" and select
"Allow access to all APIs", it doesn't actually allow access to all APIs.
If you visit the "Manage API client access" page after that
(https://www.google.com/a/cpanel/YOUR-DOMAIN/ManageOauthClients),
you'll see that there is an entry like:
YOR-DOMAIN/CONSUMER-KEY "This client has access to all APIs"
It seems that this doesn't include Provisioning API.
Only after we explicitly added the Provisioning API, the code started to work.
So to enable Provisioning API, you should also have something like the following entry in your list:
YOR-DOMAIN/CONSUMER-KEY Groups Provisioning (Read only) https://apps-apis.google.com/a/feeds/group/#readonly
User Provisioning (Read only) https://apps-apis.google.com/a/feeds/user/#readonly
Somone else had the same problem:
http://www.gnegg.ch/2010/06/google-apps-provisioning-two-legged-oauth/
Sasa
Presumably you are trying to get an unauthorised request token here? I Haven't used the Google implementation, but the OAuth 1.0a spec says you need a callback URL, which you don't have. This might be a red herring as the spec says a missing param should return HTTP code 400 not 401.
See http://oauth.net/core/1.0a/#auth_step1

Categories