AccessTokenRefreshError: API электронных таблиц Google с учетной записью службы oAuth 2.0 приложения App Engine

Я пытаюсь получить доступ к электронной таблице Google через API GData, используя учетные данные учетной записи службы oAuth 2.0, созданные для приложения Python 2.7, размещенного на Google App Engine.

  1. Приложение использует недавний gdata-python-client от Google, v. 2.0.18 (gdata и atom).
  2. Приложение использует последнюю версию google-api-python-client-gae, версия 1.2.
  3. В консоли разработчика Google для этого проекта (в этом примере это называется "my-gae-app"), я создал служебную учетную запись и делегировал полномочия на уровне домена для служебной учетной записи, как описано здесь.
  4. Нужная электронная таблица на Google Диске принадлежит домену Google Apps для работы, который здесь называется mygoogleappsdomain.com.
  5. Я предоставил доступ на чтение и запись для электронной таблицы my-gae-app@appspot.gserviceaccount.com и на адрес электронной почты, указанный для этой учетной записи службы и который назначен clientEmail Переменная в коде ниже. Не уверен, какой из двух адресов электронной почты будет действительно необходим, поэтому я назначил оба. Пользователь с impersonateUser адрес электронной почты также имеет права на чтение и запись этой таблицы.

С клиентом Google API Python AppAssertionCredentials Я могу получить доступ к метаданным нужной электронной таблицы через Google Drive API. Однако, если я пытаюсь получить доступ к содержимому таблицы с помощью gdata, я получаю ошибки. Лучший результат, который я мог получить до сих пор с учетной записью службы, - это использование SignedJwtAssertionCredentials, как предлагается здесь. Тем не менее, я застрял с этим AccessRefreshTokenError: access denied и я не понимаю, что происходит не так.

import os
import httplib2
from google.appengine.api import memcache
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client
import gdata.spreadsheet.service

# AppAssertionCredentials is not supported in gdata python client library,
# so we use SignedJwtAssertionCredentials with the credential 
# file of this service account.
# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
clientEmail = '10346........-g3dp......................3m1em8@developer.gserviceaccount.com'
p12File = 'app.p12'
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File)
impersonateUser = 'user@mygoogleappsdomain.com'
spreadsheetKey = '1mcJHJ...................................juQMw' # ID copied from URL of desired spreadsheet in Google Drive
with open(path) as f:
    privateKey = f.read()
    f.close()

# Getting credentials with AppAssertionCredentials only worked successfully
# for Google API Client Library for Python, e.g. accessing file's meta-data.
# So we use SignedJwtAssertionCredentials, as suggested in
# https://stackru.com/questions/16026286/using-oauth2-with-service-account-on-gdata-in-python
credentials = SignedJwtAssertionCredentials(
    clientEmail,
    privateKey,
    scope=(
        'https://www.googleapis.com/auth/drive.file ',
        # added the scope above as suggested somewhere else,
        # but error occurs with and with-out this scope
        'https://www.googleapis.com/auth/drive',
        'https://spreadsheets.google.com/feeds',
        'https://docs.google.com/feeds'
    ),
    sub=impersonateUser
)

http = httplib2.Http()
http = credentials.authorize(http)
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
# error will occur, wether using SpreadsheetsService() or SpreadsheetsClient()
#srv = gdata.spreadsheet.service.SpreadsheetsService()
#srv = auth2token.authorize(srv)

clt = gdata.spreadsheets.client.SpreadsheetsClient()
clt = auth2token.authorize(clt)
# Until here no errors

wks = clt.get_worksheets(spreadsheetKey)
# AccessTokenRefreshError: access_denied

Это ошибка, которую я получаю в удаленной оболочке:

s~my-gae-app> wks = clt.get_worksheets(spreadsheetKey)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "gdata/spreadsheets/client.py", line 108, in get_worksheets
    **kwargs)
  File "gdata/client.py", line 640, in get_feed
    **kwargs)
  File "gdata/client.py", line 267, in request
    uri=uri, auth_token=auth_token, http_request=http_request, **kwargs)
  File "atom/client.py", line 122, in request
    return self.http_client.request(http_request)
  File "gdata/gauth.py", line 1344, in new_request
    refresh_response = self._refresh(request_orig)
  File "gdata/gauth.py", line 1485, in _refresh
    self.credentials._refresh(httplib2.Http().request)
  File "/usr/local/lib/python2.7/dist-packages/oauth2client/client.py", line 653, in _refresh
    self._do_refresh_request(http_request)
  File "/usr/local/lib/python2.7/dist-packages/oauth2client/client.py", line 710, in _do_refresh_request
    raise AccessTokenRefreshError(error_msg)
AccessTokenRefreshError: access_denied

Я не уверен, означает ли это, что этой учетной записи службы запрещен доступ к электронной таблице или произошла ошибка при обновлении токена доступа. Вы знаете, что не так с этим кодом или настройкой?

1 ответ

Решение

Я понял это призвание SignedJwtAssertionCredentials без sub параметр (для "олицетворенного" пользователя) не выдаст AccessTokenRefreshError: access_denied

import os
import httplib2
from google.appengine.api import memcache
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client
import gdata.spreadsheet.service

# AppAssertionCredentials is not supported in gdata python client library,
# so we use SignedJwtAssertionCredentials with the credential 
# file of this service account.
# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
clientEmail = '10346........-g3dp......................3m1em8@developer.gserviceaccount.com'
p12File = 'app.p12'
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File)
impersonateUser = 'user@mygoogleappsdomain.com'
spreadsheetKey = '1mcJHJ...................................juQMw' # ID copied from URL of desired spreadsheet in Google Drive
with open(path) as f:
    privateKey = f.read()
    f.close()

# Getting credentials with AppAssertionCredentials only worked successfully
# for Google API Client Library for Python, e.g. accessing file's meta-data.
# So we use SignedJwtAssertionCredentials, as suggested in
# http://stackru.com/questions/16026286/using-oauth2-with-service-account-on-gdata-in-python
# but with-out the sub parameter!
credentials = SignedJwtAssertionCredentials(
    clientEmail,
    privateKey,
    scope=(
        'https://www.googleapis.com/auth/drive.file ',
        # added the scope above as suggested somewhere else,
        # but error occurs with and with-out this scope
        'https://www.googleapis.com/auth/drive',
        'https://spreadsheets.google.com/feeds',
        'https://docs.google.com/feeds'
    ),
#    sub=impersonateUser
)

http = httplib2.Http()
http = credentials.authorize(http)
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
# this pattern would eventually also work using SpreadsheetsService()
# SpreadsheetsService methods are different from SpreadsheetsClient, though
#srv = gdata.spreadsheet.service.SpreadsheetsService()
#srv = auth2token.authorize(srv)

clt = gdata.spreadsheets.client.SpreadsheetsClient()
clt = auth2token.authorize(clt)

wks = clt.get_worksheets(spreadsheetKey)
# works now!