0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[T-DASH] 多要素認証ログインでメール(gmail)をAPI経由で取得して、テスト自動化する

Last updated at Posted at 2024-05-09

Outline

サイトで新規会員登録や、ログインなどで多要素認証を行うものがある。
※URL認証なども正規表現を変えれば、今回の記事で対応可能

image.png

このような多要素認証をはさんだWEBのテストを行う場合、2つに対して自動化を組む必要がある。

  • テスト対象(WEB)
  • メール(gmail)

今回、gmailで受信したものに対して、認証処理を行うものとする。

技術的な課題

WEBのテストなので、gmailのweb siteに対してscriptを組めば解決できるとおもった。
実際にscriptを組んでみると以下のようなエラーになり、googleにloginできなかった。

image.png

他の記事で、自動化であることを回避する方法を試してみたが、どうしてもうまくいかない。

以下記事によると、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の有効化する必要がある

image.png

対象のプロジェクトを作成・選択する
尚、今回デフォルトで作成されている「My First Project」を使う。

image.png

image.png

OAuthの設定

image.png

スコープ

使えるAPIの範囲を指定する

API 意味
gmail.readonly メール メッセージと設定の表示

image.png

テストユーザー設定

image.png

認証情報

OAuth 2.0 クライアント IDを作成する。
アプリケーションの種類として「デスクトップアプリ」を選択し、最後にJSONファイルをダウンロードする
そのjsonファイルをcredentials.jsonに名前変更する

image.png

image.png

T-DASH

シナリオ

アソビューサイトを参考にした。
新規会員登録時に、登録時のメールアドレスに認証コードがメールされる。
それを取得して認証を行う。

image.png

構成

いくつかのファイルが今回登場する

ファイル名 意味
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}に入れる。

image.png

ライブラリの細かい設定では、pipで3つのgoogleライブラリをinstallする

pip install   google-api-python-client 
pip install   google-auth-httplib2 
pip install   google-auth-oauthlib

image.png

スクリプト

image.png

流れ的には以下のようになる

  1. メールに認証コードを飛ばす
  2. メール受信まで待つ
  3. メールから認証コードを取り出す(カスタム動作)
  4. 認証コードを入力する

なお、初めてテスト実行すると、以下のようにブラウザでgoogleのloginが求められる。
続行して、対象のアカウントでログインする必要がある。
ここは手動で処理する必要がある。

※tokenが有効である限り、再度ログインは求められない。

image.png

image.png

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?