PythonでYahoo IDトークンの取得

Yahoo IDトークンの取得用のRestクライアントをPythonで実装してみた。
先ずは、以下URLにアクセスし、でクライアントID・クライアントシークレット・コールバックURLの設定を行う。

Yahooディベロッパーネットワーク:
https://developer.yahoo.co.jp/yconnect

ログインの際にPOSTするパラメータは、ログイン画面からスクレイピングで取得することとなるが、どれが必須パラメータか不明だったので、とりあえず全てを対象にした。
※「.albatross」に関しては、ログインのサブミットの際に、Javascriptにより生成される値なので、Javascript実行箇所のパラメータをPOSTすることに注意。

```Python
#!/usr/bin/env python
# coding: utf-8

import requests
import base64
import random
import json
from bs4 import BeautifulSoup
from time import sleep

class GetAccessToken:
    def __init__(self):
        self.client_id = '*****' #クライアントID
        self.client_secret = '*****' #クライアントシークレット
        self.callback_url = '*****' #コールバックURL
        self.auth_base_url = 'https://auth.login.yahoo.co.jp'
        self.token_url = self.auth_base_url + '/yconnect/v2/token'
        self.login_base_url = 'https://login.yahoo.co.jp/config/login'
        self.account_edit_url = 'https://account.edit.yahoo.co.jp'
        self.nonce = random.randrange(10**31,10**32)
        base64_id_secret = self.client_id + ':' + self.client_secret
        base64_id_secret_byte = base64.b64encode(base64_id_secret.encode('utf-8'))
        self.auth_header = 'Basic ' + base64_id_secret_byte.decode('ascii')
        self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
        self.http_version = 'HTTP/1.1'
        self.content_type = 'application/x-www-form-urlencoded; charset=utf-8'
        self.user_name = '*****' #メールアドレス
        self.passwd = '*****' #パスワード
        self.default_cookie = 'YJTC=ZwNLYKOz0Je_CyW9SGrcmya6TuK.jMk-; YLS=v=2&p=0&n=1;'
        self.yjbfp_items = 'uaMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36langjascreen_height1080screen_width1920timezone_offset-540pluginsChrome PDF Plugin|Chrome PDF Viewer|Native Clientcanvas_imageiVBORw0KGgoAAAANSUhEUgAAABAAAAAWCAYAAADJqhx8AAAByUlEQVQ4T9WUv0vDUBSFv1AsaHUSxCylEmsddXGTgiJ2UhcFsY4OpaCIuLo4OQn+Dw61ILgFpJPoIDg4OLTWWlJKgqBYtFGMEnlP6s9apYrgnV6Sdw/3OznvKfywFNHvuhtuvTr/SGB9HXZ2YHUVGhtfgP8eYX/fxDCuGBnR8Ho9PE+wvOxSLCpvRnw99ubmFwi7uy7xOCQSCqEQ3NzA/Dz098PUFHzpwfm52OgSDucJBh26uzUWFjysrUGpZKLrVxSLGtPTZ1hWFQThq8AoFC7p6TFoaOjk4MDHysoD29snGEYLmYxKNGp+LpBOw9zcHUNDGdLpVsJhlcHBMrqex3ECUrCmQIVbVU85OnJYWtKw7TPpum1r7O15agsIDGFWPF5mYiLP4qKfw0MTv7+F42NVBqnmBEJAYExO3jM2lmNgoIlstkQkEiCV8n1fYHYWYjGTQsFCVX0yNMmk53sCAiGXc5mZsdnaytLb20Zfnyr/kKjhYat6Eis5ePJBwXGe3A+FAsRiTbJZhExkomqU318oIvMXF7dEIh1vPun6qXyuvP9wGq+v70gmM3LT+HgXzc1eubasskQSNTraSXu7T65/5zjXex+KvkcATSsyIKf0dAAAAABJRU5ErkJggg=='

    def get_az_code(self):
        s = requests.Session()

        headers = {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
            'User-Agent': self.user_agent,
            'HTTP-Version': self.http_version,
            'Content-Type': self.content_type,
            'Connection': 'Keep-Alive',
        }

        url = self.auth_base_url + '/yconnect/v2/authorization?response_type=code&client_id=' +              self.client_id + '&redirect_uri=' + self.callback_url + '&scope=openid+profile&nonce=' + str(self.nonce)
        res = s.get(url, headers=headers)

        session_str = '%26session%3D'
        session_idx = res.url.find(session_str)
        session_id = res.url[session_idx+13:session_idx+21]

        res_redirect = s.get(res.url, headers=headers, cookies=s.cookies.get_dict())

        login_form_html = BeautifulSoup(res_redirect.text, 'html.parser')
        albatross_str = 'document.getElementsByName(".albatross")[0].value ='
        albatross_idx = login_form_html.text.find(albatross_str)
        albatross = login_form_html.text[albatross_idx+53: albatross_idx+109]
        bcrumb_id_check = login_form_html.find(attrs={'name': 'bcrumb_id_check'}).get('value')
        bcrumb_send_sms = login_form_html.find(attrs={'name': 'bcrumb_send_sms'}).get('value')
        bcrumb_send_mail = login_form_html.find(attrs={'name': 'bcrumb_send_mail'}).get('value')
        u = login_form_html.find(attrs={'name': 'u'}).get('value')
        done_url = self.auth_base_url + '/yconnect/v2/authorization?.scrumb=0&from=login&session=' +              session_id + '&display=page'
        reg_url = self.account_edit_url + '/registration?src=yconnectv2&done=https%3A%2F%2Fauth.login.yahoo.co.jp%2Fyconnect%2Fv2%2Fauthorization%3F.scrumb%3D0%26from%3Dlogin%26session%3' +             session_id + '%26display%3Dpage&ckey=' + self.client_id + '&.display=page'

        data = {
            '.ct': '',
            '.display': 'page',
            '.done': done_url,
            '.keep': '',
            '.reg': reg_url,
            '.src': 'yconnectv2',
            '.suppreg_skip': '',
            '.yby': '',
            'auth_lv': 'pin',
            'card_cushion_skip': '',
            'ckey': self.client_id,
            'nolink': '',
            'nonotice': '',
            'noreg': '',
            'referrer': '',
            't_cushion': '',
            '.albatross': albatross,
            '.requiredPsCheckBox': '',
            '.slogin': '',
            '.tries': '1',
            'ls_autocomp': '',
            'showpw_status': '',
            'u': u,
            'inactive_pw': '',
            'send_sms_counts': '1',
            'send_mail_counts': '1',
            'version': '',
            'auth_method': 'pwd',
            'pwless_captchaid': '',
            'sms_token': '',
            'mail_token': '',
            'masked_dest': '',
            'bcrumb_id_check': bcrumb_id_check,
            'bcrumb_send_sms': bcrumb_send_sms,
            'bcrumb_send_mail': bcrumb_send_mail,
            'user_name': self.user_name,
            'assertionInfo': '',
            'webauthn': '',
            'fido': '0',
            'auth_list': 'pwd',
            'login': self.user_name,
            'passwd': self.passwd,
            'code': '',
            'btnSubmit': '',
            'presistent': 'y',
            'yjbfp_items': self.yjbfp_items

        }

        res_login = s.post(self.login_base_url, data=data, headers=headers, cookies=s.cookies.get_dict())

        code_str = '?code='
        code_idx = res_login.url.find(code_str)
        az_code = res_login.url[code_idx+6:code_idx+14]

        return az_code

    def get_access_token(self, az_code):

        headers = {
            'HTTP-Version': self.http_version,
            'Content-Type': self.content_type,
            'Authorization': self.auth_header
            }

        data = {
            'grant_type': 'authorization_code',
            'code': az_code,
            'redirect_uri': self.callback_url
        }

        response = requests.post(self.token_url, data=data, headers=headers)
        access_token = response.json()['access_token']
        refresh_token = response.json()['refresh_token']

        return access_token, refresh_token

    def update_access_token(self, refresh_token):
        headers = {
            'HTTP-Version': self.http_version,
            'Content-Type': self.content_type,
            'Authorization': self.auth_header
            }

        data = {
            'grant_type': 'refresh_token',
            'refresh_token': refresh_token
            }

        response = requests.post(self.token_url, data=data, headers=headers)
        u_access_token = response.json()['access_token']

        return u_access_token
```