App engine call to Google API python client return 403 with @oauth_required

298 views Asked by At

It should be a trivial job but i cannot get it out.

I need to call the Google Calendar API from Gae; thus I set all up as per Google docs and examples:

I've an /auth.py:

CLIENT_SECRETS = os.path.join(os.path.dirname(__file__), 'client_secrets.json')
SCOPES = [
    'https://www.googleapis.com/auth/calendar',
]
decorator = appengine.OAuth2DecoratorFromClientSecrets(
    filename=CLIENT_SECRETS,
    scope=SCOPES,
    cache=memcache,
    prompt='consent',
)

called by main.py functions:

class Landing(webapp2.RequestHandler):
    @auth.decorator.oauth_aware
    def get(self):
        if auth.decorator.has_credentials():
            self.redirect('/in')
        else:
            self.response.out.write('''
            etc. {}'''.format(auth.decorator.authorize_url()))

class Main(webapp2.RequestHandler):
    @auth.decorator.oauth_required
    def get(self):
        links = { ... }
        render(self, 'base.html', template_values=links)

class Calendar(webapp2.RequestHandler):
    @auth.decorator.oauth_required
    def get(self):
        service = build('calendar', 'v3', http=auth.decorator.http())
        api_request = service.events().list(calendarId='primary')
        api_response = api_request.execute()
        self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
        self.response.out.write(json.dumps(api_response, indent=4))

class PutEvent(webapp2.RequestHandler):
    @auth.decorator.oauth_required
    def post(self):
        # ...
        # http = httplib2.Http(memcache)
        service = build('calendar', 'v3') #, http=http)

        api_response = []
        for i in json.loads(self.request.get('events')):
            # ...
            event = { ... } # Google Calendar event
            api_request = service.events().insert(calendarId='primary', body=scadenza)
            api_response.append(api_request.execute(http=auth.decorator.http()))

        self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
        self.response.out.write(json.dumps(api_response, indent=4))

As you can see this is a fairly simple post requested by an Ajax jQuery call ($.post('{{ putEvent_url }}', jsonData, function( data ){ console.log(data); })...

I'm in the development server, using test@example user, and the app is authorized to access my personal account's Google Calendar.

Strange thing to me is that any call to Calendar() works as expected, but call to PutEvent() end in ERROR 500.

Looking to the end of the traceback in console:

  File "/home/pierpaolo/Devnos/whiterabbit/include/oauth2client/contrib/appengine.py", line 644, in check_oauth
    resp = method(request_handler, *args, **kwargs)
  File "/home/pierpaolo/Devnos/whiterabbit/main.py", line 211, in post
    api_response.append(api_request.execute(http=auth.decorator.http()))
  File "/home/pierpaolo/Devnos/whiterabbit/include/oauth2client/_helpers.py", line 133, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/home/pierpaolo/Devnos/whiterabbit/include/googleapiclient/http.py", line 838, in execute
    raise HttpError(resp, content, uri=self.uri)
HttpError: <HttpError 403 when requesting https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json returned "Forbidden">
INFO     2017-01-04 15:13:32,385 module.py:788] default: "POST /api/put/scadenze HTTP/1.1" 500 -

I cannot understand the

HttpError: https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json returned "Forbidden">

it looks to me I already granted the app access to my account and that Google App Engine decorators have been correctly put in place to make the OAuth2.0 thing as per https://developers.google.com/api-client-library/python/guide/google_app_engine...

EDIT: I was wondering if my trouble can be related to the way i call Google Calendar API:

     HTML/JS            GAE/Py
+------------------+
|                  |
| <form>           |
|   ...data        |
| <\JS/Ajax        |
|   $.post(...data |  -->  GAE/main.py
|                  |         @auth.decorator.oauth_required
|                  |         def post(self, data):
+------------------+           event = elaborate(data)
                               service = build('calendar', 'v3')
                               api_request = service.events()
                                             .insert(calendarId='primary',
                                                     body=event)
                               api_response = api_request
                                              .execute(auth.decorator
                                                           .http())
                               self.response(api_response)

EDIT 3: I looked a bit into oauth2client.contrib.appengine and I added some logger.debug here and there: I think the problem could be in execute(http=decorator.http()) call, but it is the same in my other handlers! Neither positional nor keyword nor put authrized Http in service build changes the misbehaviour...

Nor can I see what problem may pose _helpers.py", line 133, in positional_wrapper...

Dear all, some hint on how to research further?

Actually, I can insert Acl and/or insert a secondary calendar in the same RequestHandler that throws Forbidden exception with events().insert()...!

2

There are 2 answers

0
user2154587 On BEST ANSWER

Apparently, the problem is to try to insert an all-day event with endTimeUnspecified: True...

I opened an issue on google-api-python-client GitHub tracker: https://github.com/google/google-api-python-client/issues/334.

Maybe someone will look into it or post a more precise answer.

Thank you all.

2
Aaron On

I haven't got enough reputation to comment so I'll just leave it as an answer:

Firstly, double check that you've got calandar api enabled at https://console.cloud.google.com/apis/dashboard?project=yourproject

Secondly. I've used the contacts API and discovered that once you've granted access once you cannot be granted access again unless you first revoke the initial allowance. I encountered this when a user connected my app to Google Contacts, then disconnected, then tried to reconnect - the second reconnect would fail. To check / revoke, head to https://security.google.com/settings/security/permissions