Outline
サイトで新規会員登録や、ログインなどで多要素認証を行うものがある。
※URL認証なども正規表現を変えれば、今回の記事で対応可能
このような多要素認証をはさんだWEBのテストを行う場合、2つに対して自動化を組む必要がある。
- テスト対象(WEB)
- メール(gmail)
今回、gmailで受信したものに対して、認証処理を行うものとする。
技術的な課題
WEBのテストなので、gmailのweb siteに対してscriptを組めば解決できるとおもった。
実際にscriptを組んでみると以下のようなエラーになり、googleにloginできなかった。
他の記事で、自動化であることを回避する方法を試してみたが、どうしてもうまくいかない。
以下記事によると、googleのセキュリティ強化でseleniumからはアクセスできなくなっているようだ。
また別の方法としてIMAPにアクセスする方法もある。
robot frameworkのImapLibraryである。
しかし、こちらで接続をしてもmail serverにもよるが、セキュリティの問題でアクセスが拒否される。
gmailはdefaultでは無理だった。
gmailの場合、”安全性の低いアプリへのアクセスを管理する”でアクセスすることも可能だが、安全性や2024年秋ではサポートされなくなる。
解決方法
gmailにAPIでアクセスして、メールの情報を取りに行く。
尚、今回は固定のメールアドレスに対して、MFA情報をとることを前提とする。
アカウントの切り替えは想定していない。
(なお、gmailのaliasを使えば、その範囲のユーザー全部のメールは対象になる)
今回、T-DASHをつかって、その自動化をやってみた
gmail APIの利用
こちらから設定を行う。
尚、Google Cloudを利用するために、有料会員設定を行う必要がある。
(90日間の無料トライアルはある)
gmail APIを有効化
gmailをAPIで取得するために、対象のプロジェクトに対してAPIの有効化する必要がある
対象のプロジェクトを作成・選択する
尚、今回デフォルトで作成されている「My First Project」を使う。
OAuthの設定
スコープ
使えるAPIの範囲を指定する
API | 意味 |
---|---|
gmail.readonly | メール メッセージと設定の表示 |
テストユーザー設定
認証情報
OAuth 2.0 クライアント IDを作成する。
アプリケーションの種類として「デスクトップアプリ」を選択し、最後にJSONファイルをダウンロードする
そのjsonファイルをcredentials.jsonに名前変更する
T-DASH
シナリオ
アソビューサイトを参考にした。
新規会員登録時に、登録時のメールアドレスに認証コードがメールされる。
それを取得して認証を行う。
構成
いくつかのファイルが今回登場する
ファイル名 | 意味 |
---|---|
gmail.py | pythonライブラリ |
credentials.json | OAuth Client credentials json (上記作成した、認証情報) |
token.json | API token json (自動生成される) |
配置場所
T-DASHのcustom libraryの置き場を使う。
%HOMEPATH%\AppData\Local\Programs\T-DASH\customlib
独自ライブラリ
カスタム動作にinstallするpython script
gmail.py
import os.path
import base64
import re
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
#from robot.libraries.BuiltIn import BuiltIn
# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
# customize 1
LIBRARY_DIR = "C:/Users/xxxxx.xxxxx/AppData/Local/Programs/T-DASH/customlib/"
CREDENTIALS_PATH = LIBRARY_DIR + "credentials.json"
TOKEN_PATH = LIBRARY_DIR + "token.json"
def get_mfa_code_from_gmail():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
# customize 2
query = "アソビュー!より認証コードを送付します"
tag = "INBOX"
count = 5
creds = None
# The file token.json 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_PATH):
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
# 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_PATH, SCOPES
)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(TOKEN_PATH, "w") as token:
token.write(creds.to_json())
try:
# Call the Gmail API
service = build("gmail", "v1", credentials=creds)
messages = get_list_message(service, query, count=count)
content = messages[0]['body']
code = get_code_with_regrex(content)
print("code:",code)
global MFA_CODE
MFA_CODE = code
return code
except HttpError as error:
# TODO(developer) - Handle errors from gmail API.
print(f"An error occurred: {error}")
def decode_base64url_data(data):
"""
decode base64url
"""
decoded_bytes = base64.urlsafe_b64decode(data)
decoded_message = decoded_bytes.decode("utf-8")
return decoded_message
def get_list_message(service, query, count=3):
"""
get mail list
"""
label_ids=['INBOX']
messages = []
try:
message_ids = (
service.users().messages().list(userId="me", maxResults=count, q=query, labelIds=label_ids).execute()
)
if message_ids["resultSizeEstimate"] == 0:
print("no result data!")
return []
# get message with message id
for message_id in message_ids["messages"]:
message_detail = (
service.users().messages().get(userId="me", id=message_id["id"]).execute()
)
message = {}
message["id"] = message_id["id"]
# plain text
if 'data' in message_detail['payload']['body']:
message["body"] = decode_base64url_data(message_detail["payload"]["body"]["data"])
# html mail
else:
parts = message_detail['payload']['parts']
parts = [part for part in parts if part['mimeType'] == 'text/plain']
message["body"] = decode_base64url_data(parts[0]['body']['data'])
# subject
message["subject"] = [
header["value"]
for header in message_detail["payload"]["headers"]
if header["name"] == "Subject"
][0]
# from
message["from"] = [
header["value"]
for header in message_detail["payload"]["headers"]
if header["name"] == "From"
][0]
messages.append(message)
return messages
except errors.HttpError as error:
print(f"An error occurred: {error}")
# customize 3
def get_code_with_regrex(message):
content = message.replace('\n','').replace('\r','')
# print(content)
# 認証コード:123456
pattern = r'認証コード:?([0-9]+)'
result = re.search(pattern, content)
if result:
return result.group(1)
else:
return "no hit"
こちらのコードは、アソビューで新規会員登録した時の認証コードを受けた時、そのコードを取得するライブラリである。
自分のテストに合わせた修正をする必要がある
改修か所 | 内容 |
---|---|
# customize 1 LIBRARY_DIR | T-DASHのdata pathを実行者に合わせたものにする |
# customize 2 query | メールで検索するキーワードを指定する |
# customize 3 get_code_with_regrex | メール本文から認証コードを取得する正規表現 |
カスタム動作
カスタム動作では、get_mfa_code_from_gmailを呼び出し、結果をT-DASHで使えるように変数${MFA_CODE}に入れる。
ライブラリの細かい設定では、pipで3つのgoogleライブラリをinstallする
pip install google-api-python-client
pip install google-auth-httplib2
pip install google-auth-oauthlib
スクリプト
流れ的には以下のようになる
- メールに認証コードを飛ばす
- メール受信まで待つ
- メールから認証コードを取り出す(カスタム動作)
- 認証コードを入力する
なお、初めてテスト実行すると、以下のようにブラウザでgoogleのloginが求められる。
続行して、対象のアカウントでログインする必要がある。
ここは手動で処理する必要がある。
※tokenが有効である限り、再度ログインは求められない。