client_idとcient_secretだけからgoogle authのtoken情報を取得する方法

こんな感じでやれば良い。書きかけ。飽きたので途中でおしまい。

prepare

pip install google-auth-oauthlib python-dotenv

run

以下が必要。

  • アプリがRPに権限を要求するときのclient id, client secret
  • 権限を要求するscopes

client id, client secret

print(credentials.to_json()) の結果などはキャッシュしておくと良い。 CLIENT_IDとCLIENT_SECRETを環境変数から受け取っている。client idやclient secretの情報はこの辺りにあるはず。なければ作る。

scopes

scopeはAPIによって調整する必要がある。scopeを調べるときはこのあたりを見ると良い。

run

テキトーに自分のドライブのspreadsheetを持ってくる。google-api-python-clientは使っていない。

import os
import dotenv
import google_auth_oauthlib
import requests
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request, AuthorizedSession


def get_credentials() -> Credentials:
    dotenv.load_dotenv()
    client_id = os.environ["CLIENT_ID"]
    client_secret = os.environ["CLIENT_SECRET"]

    scopes = [
        "https://www.googleapis.com/auth/drive",
    ]

    credentials = google_auth_oauthlib.get_user_credentials(
        scopes, client_id, client_secret
    )
    return credentials


def get_session(credentials: Credentials) -> AuthorizedSession:
    return AuthorizedSession(credentials=credentials)


def iterate_spreadsheets(session: AuthorizedSession):
    page_token = ""
    url = "https://www.googleapis.com/drive/v3/files"

    params = {
        "q": "mimeType='application/vnd.google-apps.spreadsheet'",
        "pageSize": 1000,
        "supportsTeamDrives": True,
        "includeTeamDriveItems": True,
        "fields": "nextPageToken,files(name,id)",
    }

    while page_token is not None:
        if page_token:
            params["pageToken"] = page_token

        res = session.request("get", url, params=params)
        assert res.status_code == 200, res.status_code
        data = res.json()
        for file in data.get("files", []):
            yield file

        page_token = data.get("nextPageToken", None)
        if page_token is None:
            break


credentials = get_credentials()

print(credentials.to_json())
print(credentials.token)
print(credentials.valid)
print(credentials.refresh(Request()))

session = get_session(credentials)
assert isinstance(session, requests.Session), session.__class__.mro()

for file in iterate_spreadsheets(session):
    print(f'Found file: {file.get("name")} ({file.get("id")})')

こんな感じで動く。

$ python 00sheets.py
Found file: Where is the money Lebowski? (1HfOblsBJfbG0mNyXS1hbUpkG_OpOpMJTgUwDuzSA1W4)
Found file: 企業別福利厚生・就業規定・ITポリシーリスト (11DAp3g9zwcRznIf8EFIKmMR10vtRPNNDqHIwx5Zsj3I)
Found file: populations (102x3SPWP8vN4L922_GjyXJa7VenPq1AWtbRpC4PRhGk)
...

Makefile

CONF ?= ~/.config/cliauth/google-client-secrets.json
export CLIENT_ID ?= $(shell jqfpy "get('installed/client_id')" $(CONF) -r)
export CLIENT_SECRET ?= $(shell jqfpy "get('installed/client_secret')" $(CONF) -r)

00:
  python $(shell echo $@*.py)

.env ここに書くのではなくこういう感じで設定するよという例 (Makefileがあるので不要かも)。

CLIENT_ID = "xxxx"
CLIENT_SECRET = "xxxx"

gist

https://gist.github.com/podhmo/2d0baf179689db5868b32a43654a1bc0

詳細

:warning: ここから下は書きかけのメモ。

TODO: あとで書く

  • oauth2lib is depreacated, using oauthlib
  • token情報をキャッシュしておく方法
  • AuthorizedSessionは毎回refresh tokenからaccess tokenを生成し直している
  • revoke tokenの方法
  • InstalledAppFlowは run_console() ではなく run_local_server() の方が便利
  • 実は google-auth-oauthlibにコマンドが用意されている。
  • ↑の例はoauth2 client idの話
  • サービスアカウントの場合は google.authの機能を使うともっと手軽
  • ものによっては環境変数にファイルのパスを書くだけで良い

oauth2client is deprecated

https://pypi.org/project/oauth2client/

Note: oauth2client is now deprecated. No more features will be added to the libraries and the core team is turning down support. We recommend you use google-auth and oauthlib.

理由はこのあたり

https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html

認証のタイプ

tokenのキャッシュなども気にした場合

素直にドキュメントを読めば読むと良い。こちらはpython-google-api-clientを使っている例。入力が特定のf−マットに従ったファイルなのがちょっと読み替えが必要でだるいかもしれない。

import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']

# The ID and range of a sample spreadsheet.
SAMPLE_SPREADSHEET_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'
SAMPLE_RANGE_NAME = 'Class Data!A2:E'

def main():
    """Shows basic usage of the Sheets API.
    Prints values from a sample spreadsheet.
    """
    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('sheets', 'v4', credentials=creds)

    # Call the Sheets API
    sheet = service.spreadsheets()
    result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
                                range=SAMPLE_RANGE_NAME).execute()
    values = result.get('values', [])

    if not values:
        print('No data found.')
    else:
        print('Name, Major:')
        for row in values:
            # Print columns A and E, which correspond to indices 0 and 4.
            print('%s, %s' % (row[0], row[4]))

if __name__ == '__main__':
    main()

実は google-auth-oauthlibにコマンドが用意されている

$ python -m google_auth_oauthlib.tool --help
Usage: __main__.py [OPTIONS]

  Command-line tool for obtaining authorization and credentials from a user.

  This tool uses the OAuth 2.0 Authorization Code grant as described in
  section 1.3.1 of RFC6749:
  https://tools.ietf.org/html/rfc6749#section-1.3.1

  This tool is intended for assist developers in obtaining credentials for
  testing applications where it may not be possible or easy to run a
  complete OAuth 2.0 authorization flow, especially in the case of code
  samples or embedded devices without input / display capabilities.

  This is not intended for production use where a combination of companion
  and on-device applications should complete the OAuth 2.0 authorization
  flow to get authorization from the users.

Options:
  --client-secrets <client_secret_json_file>
                                  Path to OAuth2 client secret JSON file.
                                  [required]
  --scope <oauth2 scope>          API scopes to authorize access for.
                                  [required]
  --save                          Save the credentials to file.  [default:
                                  False]
  --credentials <oauth2_credentials>
                                  Path to store OAuth2 credentials.  [default:
                                  /Users/nao/Library/Application
                                  Support/google-oauthlib-
                                  tool/credentials.json]
  --headless                      Run a console based flow.  [default: False]
  --help                          Show this message and exit.