0

我試圖通過使用託管在谷歌應用程序引擎的Python 2.7的應用程序創建的OAuth 2.0服務帳戶憑據的GData API訪問谷歌電子表格的OAuth 2.0服務帳戶。AccessTokenRefreshError:谷歌電子表格API與App Engine應用

  1. 該應用程序使用最近來自Google的gdata-python-client v。2.0.18(gdata and atom)。
  2. 該應用使用最近的google-api-python-client-gae,v.1.2。
  3. 在谷歌開發者控制檯爲這個項目(在這個例子中被稱爲「我的-GAE應用內」),我創建了一個服務帳戶和授權域範圍內的機關服務帳戶as described here
  4. 所需的電子表格在Google雲端硬盤中屬於Google Apps for Work域,這裏稱爲「mygoogleappsdomain.com」。
  5. 我已授予讀+寫的電子表格訪問[email protected]併爲這個服務帳戶顯示的電子郵件地址,並分配給clientEmail變量在下面的代碼。不確定兩個電子郵件地址中哪一個實際需要,所以我分配了兩個。具有impersonateUser電子郵件地址的用戶也具有對此電子表格的讀寫權限。

使用Google API Python客戶端的AppAssertionCredentials我可以通過Google Drive API訪問所需電子表格的元數據。但是,如果我嘗試使用gdata訪問電子表格的內容,則會出現錯誤。我可以通過服務帳戶獲得的最好結果是SignedJwtAssertionCredentialsas suggested here。但是,我堅持這個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 = '[email protected]eaccount.com' 
p12File = 'app.p12' 
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File) 
impersonateUser = '[email protected]' 
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://stackoverflow.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 

這是我在遠程shell得到錯誤:

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 

我不知道這是否表明,這種服務帳戶被拒絕訪問電子表格,或者如果有一個刷新訪問令牌時出錯。你知道這個代碼或設置有什麼問題嗎?

+0

它可能進入到電子表格。將服務帳戶視爲一個人。如果隨機用戶X無權訪問該表單,則該服務帳號也不可用。我不確定你是否可以,但是嘗試服務帳戶的電子郵件,並給它訪問就像你隨機用戶X. – DaImTo

+0

我不知道在添加讀寫訪問時在電子表格文件中使用哪個電子郵件地址,所以我使用了兩個地址,即GAE應用的實際電子郵件地址,例如「[email protected]」以及服務帳戶的非常長的隱祕外觀電子郵件地址,例如「10346 ........- g3dp ...................... [email protected]」。我不確定在調用'SignedJwtAssertionCredentials()'時是否必須使用其中的一個作爲「sub」參數。目前,它在我們的Google Apps域中使用普通用戶電子郵件,該電子郵件也具有對此文件的讀寫權限。 – Ani

+1

從您創建時獲得的服務帳戶的非常長的密碼看起來的電子郵件地址。是您應該使用的那個,因爲這是服務帳戶的用戶標識。它與您下載的密鑰文件配對是構成服務帳戶登錄的內容 – DaImTo

回答

3

我已經想通了,調用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 = '[email protected]eaccount.com' 
p12File = 'app.p12' 
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File) 
impersonateUser = '[email protected]' 
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://stackoverflow.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!