Repository: yt-dlp/yt-dlp

Issue:
[TubeTuGraz] Login no longer works because now-mandatory 2FA is not supported
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting that a **supported** site is broken
- [X] I've verified that I'm running yt-dlp version **2023.03.03** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [ ] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

Austria

### Provide a description that is worded well enough to be understood

TU Graz has added mandatory Two-Factor-Authentication (2FA) to sso.tugraz.at (and therefore tube.tugraz.at), which breaks the login feature of the TubeTuGraz extractor (which was written at a time before 2FA existed at TU Graz), therefore breaking login completely.

Since I am the original developer of this extractor, this bug report serves mainly as a tracking issue until I have implemented 2FA support (via `--twofactor`) for this extractor.

Example command (from testcases):
- `yt-dlp -vU https://tube.tugraz.at/paella/ui/watch.html?id=2df6d787-e56a-428d-8ef4-d57f07eef238 --username USERNAME`
- (of course you will only be able to test this properly if you are a student at this university)

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', 'https://tube.tugraz.at/paella/ui/watch.html?id=2df6d787-e56a-428d-8ef4-d57f07eef238', '--username', 'PRIVATE']
Type account password and press [Return]: 
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2023.03.03 [934496428]
[debug] Python 3.10.9 (CPython x86_64 64bit) - Linux-6.2.1-arch1-1-x86_64-with-glibc2.37 (OpenSSL 3.0.8 7 Feb 2023, glibc 2.37)
[debug] exe versions: ffmpeg 5.1.2 (setts), ffprobe 5.1.2, rtmpdump 2.4
[debug] Optional libraries: Cryptodome-3.12.0, certifi-2022.09.24, sqlite3-2.6.0
[debug] Proxy map: {}
[debug] Loaded 1782 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Available version: stable@2023.03.03, Current version: stable@2023.03.03
yt-dlp is up to date (stable@2023.03.03)
[TubeTuGraz] downloading login page
[TubeTuGraz] logging in
WARNING: [TubeTuGraz] unable to login: incorrect password
[TubeTuGraz] Extracting URL: https://tube.tugraz.at/paella/ui/watch.html?id=2df6d787-e56a-428d-8ef4-d57f07eef238
[TubeTuGraz] 2df6d787-e56a-428d-8ef4-d57f07eef238: Downloading episode metadata
[TubeTuGraz] 2df6d787-e56a-428d-8ef4-d57f07eef238: Downloading presentation HLS manifest
[TubeTuGraz] 2df6d787-e56a-428d-8ef4-d57f07eef238: Downloading presentation DASH manifest
[TubeTuGraz] 2df6d787-e56a-428d-8ef4-d57f07eef238: Downloading presenter HLS manifest
[TubeTuGraz] 2df6d787-e56a-428d-8ef4-d57f07eef238: Downloading presenter DASH manifest
WARNING: Extractor failed to obtain "title". Creating a generic title instead
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, filesize, fs_approx, tbr, vbr, abr, asr, proto, vext, aext, hasaud, source, id
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 2df6d787-e56a-428d-8ef4-d57f07eef238: Downloading 1 format(s): p0va0br197213+p0aa0br193850
[debug] Invoking dashsegments downloader on "https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_2df6d787-e56a-428d-8ef4-d57f07eef238_presentation.smil/manifest_mpm4sav_mvlist.mpd"
[dashsegments] Total fragments: 550
[download] Destination: TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].fp0va0br197213.mp4
[download] 100% of  131.63MiB in 00:01:40 at 1.30MiB/s
[debug] Invoking dashsegments downloader on "https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_2df6d787-e56a-428d-8ef4-d57f07eef238_presenter.smil/manifest_mpm4sav_mvlist.mpd"
[dashsegments] Total fragments: 550
[download] Destination: TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].fp0aa0br193850.m4a
[download] 100% of  128.72MiB in 00:01:54 at 1.12MiB/s
[Merger] Merging formats into "TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].mp4"
[debug] ffmpeg command line: ffmpeg -y -loglevel repeat+info -i 'file:TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].fp0va0br197213.mp4' -i 'file:TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].fp0aa0br193850.m4a' -c copy -map 0:v:0 -map 1:a:0 -movflags +faststart 'file:TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].temp.mp4'
Deleting original file TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].fp0va0br197213.mp4 (pass -k to keep)
Deleting original file TubeTuGraz video #2df6d787-e56a-428d-8ef4-d57f07eef238 [2df6d787-e56a-428d-8ef4-d57f07eef238].fp0aa0br193850.m4a (pass -k to keep)
```



Discussion:
I intend to fix this bug, I'm looking at the requests needed to do 2FA right now.

Resolving patch:
diff --git a/yt_dlp/extractor/tubetugraz.py b/yt_dlp/extractor/tubetugraz.py
--- a/yt_dlp/extractor/tubetugraz.py
+++ b/yt_dlp/extractor/tubetugraz.py
@@ -21,17 +21,36 @@ def _perform_login(self, username, password):
         if not urlh:
             return
 
-        urlh = self._request_webpage(
+        content, urlh = self._download_webpage_handle(
             urlh.geturl(), None, fatal=False, headers={'referer': urlh.geturl()},
-            note='logging in', errnote='unable to log in', data=urlencode_postdata({
+            note='logging in', errnote='unable to log in',
+            data=urlencode_postdata({
                 'lang': 'de',
                 '_eventId_proceed': '',
                 'j_username': username,
                 'j_password': password
             }))
+        if not urlh or urlh.geturl() == 'https://tube.tugraz.at/paella/ui/index.html':
+            return
 
-        if urlh and urlh.geturl() != 'https://tube.tugraz.at/paella/ui/index.html':
+        if not self._html_search_regex(
+                r'<p\b[^>]*>(Bitte geben Sie einen OTP-Wert ein:)</p>',
+                content, 'TFA prompt', default=None):
             self.report_warning('unable to login: incorrect password')
+            return
+
+        content, urlh = self._download_webpage_handle(
+            urlh.geturl(), None, fatal=False, headers={'referer': urlh.geturl()},
+            note='logging in with TFA', errnote='unable to log in with TFA',
+            data=urlencode_postdata({
+                'lang': 'de',
+                '_eventId_proceed': '',
+                'j_tokenNumber': self._get_tfa_info(),
+            }))
+        if not urlh or urlh.geturl() == 'https://tube.tugraz.at/paella/ui/index.html':
+            return
+
+        self.report_warning('unable to login: incorrect TFA code')
 
     def _extract_episode(self, episode_info):
         id = episode_info.get('id')


Test patch: