7

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

Community
  • 1
  • 1
Luca Matteis
  • 29,161
  • 19
  • 114
  • 169
  • Is this Java? With `var`? You may want to use the Google-provided OAuth/API libraries, which will remove some opportunities for user error. http://code.google.com/p/google-api-java-client/ – Jason Hall Oct 02 '12 at 14:15
  • Hrm, but it's a simple REST call. I don't wanna use a full blown library just for a REST call. Yes it's still the JVM (Rhino). But I can use the same APIs. – Luca Matteis Oct 02 '12 at 14:40
  • `addRequestProperty` adds a header and not a query parameter, right? Maybe try logging the access token you get back and trying a request with that token manually? – Jason Hall Oct 02 '12 at 15:34
  • Shouldn't you be using "Bearer" in Authorization header instead of OAuth? – alex Oct 02 '12 at 16:43
  • @JasonHall I edited question showing that the same exact code works if I use a different API, such as urlshortener. – Luca Matteis Oct 03 '12 at 07:42
  • @LucaMatteis Have you added your App Engine service account to the list of APIs Console project members? – Jason Hall Oct 11 '12 at 19:12
  • @JasonHall yes I have... I've added the email that I have in my "Application Settings" page to my API console Team members section. Still I get the same error. – Luca Matteis Oct 11 '12 at 21:34

5 Answers5

6

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.

pinoyyid
  • 21,499
  • 14
  • 64
  • 115
  • I can read my urlshortener history using the API, so it can't be anonymous, can it? – Luca Matteis Oct 06 '12 at 11:58
  • Is there a way to log what headers are being sent? I have no http server laying around. – Luca Matteis Oct 06 '12 at 12:42
  • Do you get the same error when using the Development Server, or only on appspot.com? If you have the error on Dev, you can set up a server on localhost and use that. Another way you could do it would be to change the Drive URL from https to http and capture the traffic using Wireshark. I don't know if sending to http is supported, but it doesn't matter since you are only interested in seeing your request headers. – pinoyyid Oct 06 '12 at 12:56
  • 1
    I was able to log my HTTP headers using my same Appengine server. Here's the output: `Content-Type: application/json Authorization: Bearer xxx Host: xxx.appspot.com User-Agent: AppEngine-Google; (+http://code.google.com/appengine; appid: s~xxx) X-AppEngine-Country: US X-AppEngine-Region: ? X-AppEngine-City: ? X-AppEngine-CityLatLong: 0.000000,0.000000` – Luca Matteis Oct 06 '12 at 13:03
  • 1
    So the `Authorization` header is being set. – Luca Matteis Oct 06 '12 at 13:03
1

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.

alex
  • 2,450
  • 16
  • 22
  • hi alex. I tried changing it to Bearer, and also passed the `access_token` directly in the url, but I'm getting the same error. Would you know if there's extra things I need to setup in the Api Console page? Do I need to enable the Drive SDK as well? I can't find any relevant info about this on the web. – Luca Matteis Oct 03 '12 at 07:16
  • You can also check my EDITED question. I show how the API works if I use a different one - such as the urlshortener. So the authentication part is fine? – Luca Matteis Oct 03 '12 at 08:06
  • Sorry, I took it for granted you have Drive API enabled. Haven't you? Plus, don't forget to add your app to the Team members list tab on the API Console. – alex Oct 03 '12 at 16:24
  • Note that there's Drive SDK and there's Drive API. Judging from your code you only need the latter I guess. – alex Oct 03 '12 at 16:54
  • what do you mean team members? How do I do this? I haven't touched the team members part. – Luca Matteis Oct 04 '12 at 00:39
  • Ok I found the `foo@appspot.gserviceaccount.com` Service Account for my appengine app. I added this to my Team Members page. Still nothing is changing. Something's telling me I have to add it to the `Service Accounts` part on the bottom of the Team page, but there's no 'add' button :( This is entirely confusing and Google isn't helping me – Luca Matteis Oct 04 '12 at 00:41
1

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.

  • GoogleCredential: http://javadoc.google-api-java-client.googlecode.com/hg/1.11.0-beta/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.html and AppIdentityCredential: http://javadoc.google-api-java-client.googlecode.com/hg/1.11.0-beta/com/google/api/client/googleapis/extensions/appengine/auth/oauth2/AppIdentityCredential.html – Jason Hall Oct 03 '12 at 15:17
  • Does this work for you? I want to avoid using a full blown library just for a simple REST call. I wonder why you can't set the `serviceAccountId` from within the App Engine App Identity API. – Luca Matteis Oct 03 '12 at 15:24
  • I have it working with a serviceAccountId but you could use the AppIdentityCredential too. I think this is they quickest way of doing this. If you don't want to use the library, make sure you read the documentation provided here https://developers.google.com/accounts/docs/OAuth2 and create your own implementation. I doubt if it is going to be more effecient (at least not time wise) – Stefan Hogendoorn Oct 03 '12 at 15:46
  • @StefanHogendoorn did you have to set anything up in the API Console for this to work? I have Drive API enabled. I'm thinking it's something extra I have to do on the permissions side of things? – Luca Matteis Oct 03 '12 at 18:56
  • The internal App Engine API should handle all this for me, that's what I keep reading online. So there must be something I'm missing on the permissions side of things - like I need to grant access to my Service Account somehow. – Luca Matteis Oct 04 '12 at 07:29
  • @LucaMatteis Did you enable both the drive API and SDK? Are you getti ng errors? – Stefan Hogendoorn Oct 04 '12 at 14:46
  • @StefanHogendoorn yes I did enable them. Both. The error is the one posted in the question. – Luca Matteis Oct 04 '12 at 14:50
  • @LucaMatteis have you tried something like this: `private static Drive getAICDrive() { try { AppIdentityCredential aic = new AppIdentityCredential(DriveScopes.DRIVE, DriveScopes.DRIVE_FILE); HttpTransport httpTransport = new NetHttpTransport(); JacksonFactory jsonFactory = new JacksonFactory(); Drive d = new Drive(httpTransport, jsonFactory, aic); return d; } catch (Exception ex) { ex.printStackTrace(); return null; } }` – Stefan Hogendoorn Oct 05 '12 at 09:40
  • @StefanHogendoorn I'm not using the Google Java Client. It's a simple REST call, so I want to understand why it's not working using raw HTTP request. – Luca Matteis Oct 06 '12 at 13:20
1

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();
0

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.

Community
  • 1
  • 1
proppy
  • 10,495
  • 5
  • 37
  • 66
  • Right yeah, I could just use a regular OAuth library. But I wanted to understand why App Identity isn't working with the Drive API. Wouldn't it be a bug? – Luca Matteis Oct 12 '12 at 10:38
  • Luca, I believe this video will definitely help you resolve the issue: http://www.youtube.com/watch?v=iK14bfd6qhs – alex Oct 30 '12 at 09:32