PythonからOAuth2.0を利用してスプレッドシートにアクセスする


# 背景

Google APIの利用について、GoogleのユーザーIDとパスワードでのアクセスが2015年5月に禁止された。現在はOAuth2.0を利用してアクセスしなければならない。PythonからOAuth2.0での認証に関して、意外と情報が少なく手間取ったのでやり方を残しておく。
本記事はスプレッドシートに関してのものだが、gdataライブラリを利用する他のAPIにも同じように認証が可能なのではないかと思う(未確認)。

# 環境

python 2.7.5
MacOS X 10.9.5

# OAuth2.0での認証方法

OAuth2.0での認証のためには、いくつか準備しなければいけないことがある。
順番に手順を残しておく。

## 1. Drive APIの有効化

スプレッドシートは、Google Apps APIのDrive APIを利用して操作することになるので、以下の操作で必要なAPIを有効にする。

[Developers Console](https://console.developers.google.com/project)にアクセスして、プロジェクトを選択。
プロジェクトが存在しない場合は、適当な名前で一つ作成する。

![プロジェクト.png](https://qiita-image-store.s3.amazonaws.com/0/62874/a5f57fbf-1a33-a72f-1c7c-db9601c6a408.png "プロジェクト.png")

左側のメニューから「API」を選ぶ。

![概要_-_neoimagewall.png](https://qiita-image-store.s3.amazonaws.com/0/62874/c4b35b53-eb3e-bae2-289a-f7614cabf8c2.png "概要_-_neoimagewall.png")

「Drive API」は右の方。

![API_ライブラリ_-_neoimagewall.png](https://qiita-image-store.s3.amazonaws.com/0/62874/8aa70bc1-c454-b5bf-0a66-592c0295b608.png "API_ライブラリ_-_neoimagewall.png")

「APIを有効にする」

![Drive_API_-_API_Project.png](https://qiita-image-store.s3.amazonaws.com/0/62874/d6b6326b-68c4-e938-e89d-5118340a589a.png "Drive_API_-_API_Project.png")

有効になった。

![Drive_API_-_API_Project.png](https://qiita-image-store.s3.amazonaws.com/0/62874/d4d03e34-6f07-b023-0058-e31f2500233f.png "Drive_API_-_API_Project.png")

## 2. OAuth用クライアントIDの作成

次は、OAuthで利用するためのクライアントIDと鍵を作成しなければいけない。やり方を載せる。

同じく[Developers Console](https://console.developers.google.com/project)でプロジェクトを開いた画面で、左側のメニューから「認証情報」を選択。

![Drive_API_-_API_Project.png](https://qiita-image-store.s3.amazonaws.com/0/62874/0d207395-6a00-87bc-8683-4045fa1ec07e.png "Drive_API_-_API_Project.png")

「新しいクライアントIDを作成」

![認証情報_-_kumaproduct.png](https://qiita-image-store.s3.amazonaws.com/0/62874/d2ca1675-0059-d1eb-fed3-dc36603d1d7f.png "認証情報_-_kumaproduct.png")

「サービス アカウント」を選んで「クライアントIDを作成」

![認証情報_-_kumaproduct.png](https://qiita-image-store.s3.amazonaws.com/0/62874/696728e8-e191-994b-a45f-0e4d2aa308d7.png "認証情報_-_kumaproduct.png")

ローカルにjsonファイルがダウンロードされ、完了のダイアログが表示される。
(今回の手順ではこのjsonファイルは利用しない)

![認証情報_-_kumaproduct.png](https://qiita-image-store.s3.amazonaws.com/0/62874/d9649b58-d78c-db75-596a-538a334af0f0.png "認証情報_-_kumaproduct.png")

クライアントIDが発行されたことを確認。
「新しいP12キーを生成」を選び、秘密鍵(.p12)をダウンロードする。

![認証情報_-_kumaproduct.png](https://qiita-image-store.s3.amazonaws.com/0/62874/db733fc6-ba67-9e96-afef-d04215b80438.png "認証情報_-_kumaproduct.png")

秘密鍵がローカルに保存された。(保存された秘密鍵を「MyProject.p12」とする)

![認証情報_-_kumaproduct.png](https://qiita-image-store.s3.amazonaws.com/0/62874/1fdd9b71-97ae-f0cc-09a2-578e4131519d.png "認証情報_-_kumaproduct.png")

## 3. スプレッドシートの共有設定

上記の手順で作成したクライアントIDに対して、SpreadSheet側で共有設定を行う必要がある。

まずは、発行したクライアントIDのメールアドレスを控えておく。

![9184c188-2ade-9e97-7d1e-fb1ed6a2eaf0.png](https://qiita-image-store.s3.amazonaws.com/0/62874/617467b2-69ae-60d8-24dc-28006f10063e.png "9184c188-2ade-9e97-7d1e-fb1ed6a2eaf0.png")

APIでアクセスしたいスプレッドシートを開いて、「共有」を選択。

![無題スプレッドシート_-_Google_スプレッドシート.png](https://qiita-image-store.s3.amazonaws.com/0/62874/41167cec-322b-c085-aa0a-64fb7ce4e264.png "無題スプレッドシート_-_Google_スプレッドシート.png")

発行されたメールアドレスを入力して「送信」。

![無題スプレッドシート_-_Google_スプレッドシート.png](https://qiita-image-store.s3.amazonaws.com/0/62874/5154d50f-358b-cf20-d93c-17a71d6f11bd.png "無題スプレッドシート_-_Google_スプレッドシート.png")

これで共有設定が完了した。


## 4. ソースコード

さてようやく準備が整った。pythonのコードでアクセスする。

まず、必要なライブラリをインストール。

```sh:
$ pip install gdata oauth2client
```

認証部分は、次のようなソースになる。

```py:oauth2.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client

# 認証に必要な情報
client_email = "123456789000-abc123def456@developer.gserviceaccount.com" # 手順2で発行されたメールアドレス
with open("MyProject.p12") as f: private_key = f.read() # 手順2で発行された秘密鍵

# 認証情報の作成
scope = ["https://spreadsheets.google.com/feeds"]
credentials = SignedJwtAssertionCredentials(client_email, private_key,
    scope=scope)

# スプレッドシート用クライアントの準備
client = gdata.spreadsheets.client.SpreadsheetsClient()

# OAuth2.0での認証設定
auth_token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
auth_token.authorize(client)

# ---- これでライブラリを利用してスプレッドシートにアクセスできる ---- #

# ワークシートの取得
sheets = client.get_worksheets("1TAVVsyhCM_nprkpa0-LGWBheaXt_ipX84fIIhJw2fa0") # スプレッドシートIDを指定
for sheet in sheets.entry:
    print sheet.get_worksheet_id(), sheet.title
```

最後のprint文がこんな感じで出力されたら正常にアクセスできた証拠。

```sh:
od6 <ns0:title xmlns:ns0="http://www.w3.org/2005/Atom">&#12471;&#12540;&#12488;1</ns0:title>
```

# 遭遇したエラー

## PKCS12 format is not supported by the PyCrypto library.

### エラー内容

```
PKCS12 format is not supported by the PyCrypto library. Try converting to a "PEM" (openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) or using PyOpenSSL if native code is an option.
```

以下の部分で起きたエラー。

```
with open("MyProject.p12") as f
```

### 対応

エラー文に載っているようにして秘密鍵の形式を変更すればよい。

```sh:
$ openssl pkcs12 -in MyProject.p12 -nodes -nocerts > MyProject.pem
Enter Import Password: #「notasecret」と入力
```

それからコード中で読み込むファイルを変える。

```
with open("MyProject.pem") as f
```

# 参考

GoogleAppEngine - Google App Engine for PHPでGoogle Drive APIを使う - Qiita
http://qiita.com/hikoalpha/items/04ef84cd5f035ff64f23

python - Using Spreadsheet API OAuth2 with Certificate Authentication - Stack Overflow
http://stackoverflow.com/questions/20209178/using-spreadsheet-api-oauth2-with-certificate-authentication/20211057#20211057

Using OAuth 2.0 for Server to Server Applications   |   Google Identity Platform   |   Google Developers
https://developers.google.com/identity/protocols/OAuth2ServiceAccount?hl=ja

OAuth 2.0   |   API Client Library for Python   |   Google Developers
https://developers.google.com/api-client-library/python/guide/aaa_oauth?hl=ja

google app engine - SignedJwtAssertionCredentials on AppEngine doesn't recognize PEM key - Stack Overflow
http://stackoverflow.com/questions/17993604/signedjwtassertioncredentials-on-appengine-doesnt-recognize-pem-key

gdata: gdata.spreadsheets.client Namespace Reference - doxygen documentation | Fossies Dox
http://fossies.org/dox/gdata-2.0.18/namespacegdata_1_1spreadsheets_1_1client.html