Merge remote-tracking branch 'riking/twofactor'
This commit is contained in:
commit
9480d1a566
@ -318,6 +318,8 @@ def parseOpts(overrideArguments=None):
|
|||||||
dest='username', metavar='USERNAME', help='account username')
|
dest='username', metavar='USERNAME', help='account username')
|
||||||
authentication.add_option('-p', '--password',
|
authentication.add_option('-p', '--password',
|
||||||
dest='password', metavar='PASSWORD', help='account password')
|
dest='password', metavar='PASSWORD', help='account password')
|
||||||
|
authentication.add_option('-2', '--twofactor',
|
||||||
|
dest='twofactor', metavar='TWOFACTOR', help='two-factor auth code')
|
||||||
authentication.add_option('-n', '--netrc',
|
authentication.add_option('-n', '--netrc',
|
||||||
action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
|
action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
|
||||||
authentication.add_option('--video-password',
|
authentication.add_option('--video-password',
|
||||||
@ -752,6 +754,7 @@ def _real_main(argv=None):
|
|||||||
'usenetrc': opts.usenetrc,
|
'usenetrc': opts.usenetrc,
|
||||||
'username': opts.username,
|
'username': opts.username,
|
||||||
'password': opts.password,
|
'password': opts.password,
|
||||||
|
'twofactor': opts.twofactor,
|
||||||
'videopassword': opts.videopassword,
|
'videopassword': opts.videopassword,
|
||||||
'quiet': (opts.quiet or any_printing),
|
'quiet': (opts.quiet or any_printing),
|
||||||
'no_warnings': opts.no_warnings,
|
'no_warnings': opts.no_warnings,
|
||||||
|
@ -440,6 +440,22 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
return (username, password)
|
return (username, password)
|
||||||
|
|
||||||
|
def _get_tfa_info(self):
|
||||||
|
"""
|
||||||
|
Get the two-factor authentication info
|
||||||
|
TODO - asking the user will be required for sms/phone verify
|
||||||
|
currently just uses the command line option
|
||||||
|
If there's no info available, return None
|
||||||
|
"""
|
||||||
|
if self._downloader is None:
|
||||||
|
return None
|
||||||
|
downloader_params = self._downloader.params
|
||||||
|
|
||||||
|
if downloader_params.get('twofactor', None) is not None:
|
||||||
|
return downloader_params['twofactor']
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
# Helper functions for extracting OpenGraph info
|
# Helper functions for extracting OpenGraph info
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _og_regexes(prop):
|
def _og_regexes(prop):
|
||||||
|
@ -37,6 +37,7 @@ from ..utils import (
|
|||||||
class YoutubeBaseInfoExtractor(InfoExtractor):
|
class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||||
"""Provide base functions for Youtube extractors"""
|
"""Provide base functions for Youtube extractors"""
|
||||||
_LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
|
_LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
|
||||||
|
_TWOFACTOR_URL = 'https://accounts.google.com/SecondFactor'
|
||||||
_LANG_URL = r'https://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'
|
_LANG_URL = r'https://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'
|
||||||
_AGE_URL = 'https://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
|
_AGE_URL = 'https://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
|
||||||
_NETRC_MACHINE = 'youtube'
|
_NETRC_MACHINE = 'youtube'
|
||||||
@ -50,12 +51,19 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
fatal=False))
|
fatal=False))
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
|
"""
|
||||||
|
Attempt to log in to YouTube.
|
||||||
|
True is returned if successful or skipped.
|
||||||
|
False is returned if login failed.
|
||||||
|
|
||||||
|
If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised.
|
||||||
|
"""
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
# No authentication to be performed
|
# No authentication to be performed
|
||||||
if username is None:
|
if username is None:
|
||||||
if self._LOGIN_REQUIRED:
|
if self._LOGIN_REQUIRED:
|
||||||
raise ExtractorError(u'No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
raise ExtractorError(u'No login info available, needed for using %s.' % self.IE_NAME, expected=True)
|
||||||
return False
|
return True
|
||||||
|
|
||||||
login_page = self._download_webpage(
|
login_page = self._download_webpage(
|
||||||
self._LOGIN_URL, None,
|
self._LOGIN_URL, None,
|
||||||
@ -73,6 +81,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
u'Email': username,
|
u'Email': username,
|
||||||
u'GALX': galx,
|
u'GALX': galx,
|
||||||
u'Passwd': password,
|
u'Passwd': password,
|
||||||
|
|
||||||
u'PersistentCookie': u'yes',
|
u'PersistentCookie': u'yes',
|
||||||
u'_utf8': u'霱',
|
u'_utf8': u'霱',
|
||||||
u'bgresponse': u'js_disabled',
|
u'bgresponse': u'js_disabled',
|
||||||
@ -88,6 +97,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
u'uilel': u'3',
|
u'uilel': u'3',
|
||||||
u'hl': u'en_US',
|
u'hl': u'en_US',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert to UTF-8 *before* urlencode because Python 2.x's urlencode
|
# Convert to UTF-8 *before* urlencode because Python 2.x's urlencode
|
||||||
# chokes on unicode
|
# chokes on unicode
|
||||||
login_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k,v in login_form_strs.items())
|
login_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k,v in login_form_strs.items())
|
||||||
@ -99,6 +109,68 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
note=u'Logging in', errnote=u'unable to log in', fatal=False)
|
note=u'Logging in', errnote=u'unable to log in', fatal=False)
|
||||||
if login_results is False:
|
if login_results is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if re.search(r'id="errormsg_0_Passwd"', login_results) is not None:
|
||||||
|
raise ExtractorError(u'Please use your account password and a two-factor code instead of an application-specific password.', expected=True)
|
||||||
|
|
||||||
|
# Two-Factor
|
||||||
|
# TODO add SMS and phone call support - these require making a request and then prompting the user
|
||||||
|
|
||||||
|
if re.search(r'(?i)<form[^>]* id="gaia_secondfactorform"', login_results) is not None:
|
||||||
|
tfa_code = self._get_tfa_info()
|
||||||
|
|
||||||
|
if tfa_code is None:
|
||||||
|
self._downloader.report_warning(u'Two-factor authentication required. Provide it with --twofactor <code>')
|
||||||
|
self._downloader.report_warning(u'(Note that only TOTP (Google Authenticator App) codes work at this time.)')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Unlike the first login form, secTok and timeStmp are both required for the TFA form
|
||||||
|
|
||||||
|
match = re.search(r'id="secTok"\n\s+value=\'(.+)\'/>', login_results, re.M | re.U)
|
||||||
|
if match is None:
|
||||||
|
self._downloader.report_warning(u'Failed to get secTok - did the page structure change?')
|
||||||
|
secTok = match.group(1)
|
||||||
|
match = re.search(r'id="timeStmp"\n\s+value=\'(.+)\'/>', login_results, re.M | re.U)
|
||||||
|
if match is None:
|
||||||
|
self._downloader.report_warning(u'Failed to get timeStmp - did the page structure change?')
|
||||||
|
timeStmp = match.group(1)
|
||||||
|
|
||||||
|
tfa_form_strs = {
|
||||||
|
u'continue': u'https://www.youtube.com/signin?action_handle_signin=true&feature=sign_in_button&hl=en_US&nomobiletemp=1',
|
||||||
|
u'smsToken': u'',
|
||||||
|
u'smsUserPin': tfa_code,
|
||||||
|
u'smsVerifyPin': u'Verify',
|
||||||
|
|
||||||
|
u'PersistentCookie': u'yes',
|
||||||
|
u'checkConnection': u'',
|
||||||
|
u'checkedDomains': u'youtube',
|
||||||
|
u'pstMsg': u'1',
|
||||||
|
u'secTok': secTok,
|
||||||
|
u'timeStmp': timeStmp,
|
||||||
|
u'service': u'youtube',
|
||||||
|
u'hl': u'en_US',
|
||||||
|
}
|
||||||
|
tfa_form = dict((k.encode('utf-8'), v.encode('utf-8')) for k,v in tfa_form_strs.items())
|
||||||
|
tfa_data = compat_urllib_parse.urlencode(tfa_form).encode('ascii')
|
||||||
|
|
||||||
|
tfa_req = compat_urllib_request.Request(self._TWOFACTOR_URL, tfa_data)
|
||||||
|
tfa_results = self._download_webpage(
|
||||||
|
tfa_req, None,
|
||||||
|
note=u'Submitting TFA code', errnote=u'unable to submit tfa', fatal=False)
|
||||||
|
|
||||||
|
if tfa_results is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if re.search(r'(?i)<form[^>]* id="gaia_secondfactorform"', tfa_results) is not None:
|
||||||
|
self._downloader.report_warning(u'Two-factor code expired. Please try again, or use a one-use backup code instead.')
|
||||||
|
return False
|
||||||
|
if re.search(r'(?i)<form[^>]* id="gaia_loginform"', tfa_results) is not None:
|
||||||
|
self._downloader.report_warning(u'unable to log in - did the page structure change?')
|
||||||
|
return False
|
||||||
|
if re.search(r'smsauth-interstitial-reviewsettings', tfa_results) is not None:
|
||||||
|
self._downloader.report_warning(u'Your Google account has a security notice. Please log in on your web browser, resolve the notice, and try again.')
|
||||||
|
return False
|
||||||
|
|
||||||
if re.search(r'(?i)<form[^>]* id="gaia_loginform"', login_results) is not None:
|
if re.search(r'(?i)<form[^>]* id="gaia_loginform"', login_results) is not None:
|
||||||
self._downloader.report_warning(u'unable to log in: bad username or password')
|
self._downloader.report_warning(u'unable to log in: bad username or password')
|
||||||
return False
|
return False
|
||||||
|
Loading…
Reference in New Issue
Block a user