GoogleのOOB(OAuth out-of-band)対策

Linux

はじめに

Googleから「Migrate your OAuth out-of-band flow to an alternative method before Oct. 3, 2022」のメールが届きました。
Googleで発行するOAuth2認証に関する内容のようです。

DjangoでWebアプリケーションを作るときにメール送信を行う機能を実装することがあります。
これまでは、デスクトップアプリとして作成してflow.run_console()でトークン生成のURLを生成して、他のパソコンでトークンを発行していましたが禁止になったようです。

新規に認証情報を作成しても上記の方法ではトークンを発行時にエラーになります。
代わりにflow.run_local_server(port=0)を使い、Webアプリケーションを実行するサーバ上でトークンを生成すれば良いようです。
今回、このためだけにサーバにGUI環境をインストールしました。

環境

Debian bullseye 64bit
Python 3.10.2

GUIのインストール

リモートデスクトップで行うのでxrdpもインストールします。
完了後にOSをrebootします。

sudo apt install task-gnome-desktop
sudo apt install xrdp tigervnc-standalone-server
sudo systemctl enable xrdp

Debian11の場合、デスクトップ環境をインストールするとスリープ設定が有効(20分)になり、アクセスできなくなるので無効化します。

Debian12とサスペンド無効

スクリプト

下記のスクリプトは、指定した宛先にメールを送信するスクリプトです。
トークンを発行するためにGoogle Cloud Consoleでプロジェクトを作成、jsonファイルをダウンロードしてcredentials.jsonにファイル名を変更後にスクリプトと同じディレクトリに保存します。

リモートデスクトップで作成したアプリケーションを実行するサーバにログインします。
下記のスクリプトのOAuth認証情報を作成したGoogleアカウント、メールの差出人、メールの宛先を修正してから、ターミナルでスクリプトを実行します。

python スクリプト名

flow.run_local_server()を使うと自動でWebブラウザが起動します。
flow.run_console()を使っても、サーバ上でWebブラウザを起動してから表示されたURLを貼り付ければトークンが発行されます。

Googleにもサンプルがあります。

Python quickstart  |  People API  |  Google for Developers
import base64
import os
from email.mime.text import MIMEText

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

API_SERVICE_NAME    = 'gmail'
API_VERSION         = 'v1'
SCOPES              = ['https://www.googleapis.com/auth/gmail.send']
CLIENT_SECRETS_FILE = '/home/hoge/credentials.json'
TOKEN_PICKLE_FILE   = '/home/hoge/token.pickle'

def get_credential():
    creds = None
    if os.path.exists(TOKEN_PICKLE_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_PICKLE_FILE, SCOPES)
    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(CLIENT_SECRETS_FILE, SCOPES)
            # Webブラウザが起動して認証する
            creds = flow.run_local_server(port=0)
            # コンソールにURL表示
           #creds = flow.run_console()
        with open(TOKEN_PICKLE_FILE, 'w') as token:
            token.write(creds.to_json())
    return creds

def create_message(_subject, _from, _msg, _to):
    message             = MIMEText(_msg)
    message['from']     = _from
    message['to']       = _to
    message['subject']  = _subject

    encode_message = base64.urlsafe_b64encode(message.as_bytes())
    return {'raw': encode_message.decode()}

def main():
    creds = get_credential()
   #service = build("gmail", "v1", credentials=creds, cache_discovery=False)
   #service = build('gmail', 'v1', credentials=creds)
    service = build(API_SERVICE_NAME, API_VERSION, credentials=creds, cache_discovery=False)

    try:
        result = service.users().messages().send(
            # OAuth認証情報を作成したGoogleアカウント
            userId = 'test@abc.jp',
            # 第1引数:Subject 第2引数:メール差出人 第3引数:メール委宛先
            body   = create_message('test', 'from@abc.jp', 'message', 'to@abc.jp')
        ).execute()
    except errors.HttpError as error:
        print('An error occurred: %s' % error)

if __name__ == '__main__':
    main()

CUIに設定

トークン生成後はGUIは不要なので、CUIに切り替えます。

sudo systemctl set-default multi-user.target

GUIに切り替える場合は、下記となります。

sudo systemctl set-default graphical.target

Comments