From 647c77c1a7cd3adcad6715b3e9a8b3603e5d9579 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Fri, 16 Aug 2024 11:10:00 +0000
Subject: [PATCH] add oauth2 as auth plugin, fixes #4482

Closes #4482

Merge request studip/studip!3266
---
 composer.json                                 |   3 +-
 composer.lock                                 | 443 +++++++++++++++++-
 config/config_defaults.inc.php                |  30 +-
 lib/classes/auth_plugins/StudipAuthOAuth2.php | 113 +++++
 lib/phplib/Seminar_Auth.php                   |   8 +-
 lib/seminar_open.php                          |   9 +
 6 files changed, 598 insertions(+), 8 deletions(-)
 create mode 100644 lib/classes/auth_plugins/StudipAuthOAuth2.php

diff --git a/composer.json b/composer.json
index e7fb7e5d2b7..4a8096741b2 100644
--- a/composer.json
+++ b/composer.json
@@ -123,7 +123,8 @@
         "symfony/polyfill-php83": "1.30.0",
         "symfony/polyfill-php84": "1.30.0",
         "nyholm/psr7": "1.8.1",
-        "nyholm/psr7-server": "1.1.0"
+        "nyholm/psr7-server": "1.1.0",
+        "league/oauth2-client": "2.7.0"
     },
     "replace": {
         "symfony/polyfill-php73": "*",
diff --git a/composer.lock b/composer.lock
index 8af2df65860..0cc31d19a4a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "4d8cd43aecf3277942d94871e22bf861",
+    "content-hash": "cad2a823e38968efd43dc8b63fdd8812",
     "packages": [
         {
             "name": "algo26-matthias/idna-convert",
@@ -360,6 +360,331 @@
             ],
             "time": "2023-11-12T22:16:48+00:00"
         },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.9.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
+                "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+                "guzzlehttp/psr7": "^2.7.0",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.2 || ^3.0"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-curl": "*",
+                "guzzle/client-integration-tests": "3.0.2",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-24T11:22:20+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+                "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-18T10:29:17+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+                "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "http-interop/http-factory-tests": "0.9.0",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/2.7.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-18T11:15:46+00:00"
+        },
         {
             "name": "illuminate/collections",
             "version": "v10.48.12",
@@ -1019,6 +1344,76 @@
             },
             "time": "2018-11-26T11:52:41+00:00"
         },
+        {
+            "name": "league/oauth2-client",
+            "version": "2.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/oauth2-client.git",
+                "reference": "160d6274b03562ebeb55ed18399281d8118b76c8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8",
+                "reference": "160d6274b03562ebeb55ed18399281d8118b76c8",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "^6.0 || ^7.0",
+                "paragonie/random_compat": "^1 || ^2 || ^9.99",
+                "php": "^5.6 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.3.5",
+                "php-parallel-lint/php-parallel-lint": "^1.3.1",
+                "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5",
+                "squizlabs/php_codesniffer": "^2.3 || ^3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-2.x": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\OAuth2\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Alex Bilbie",
+                    "email": "hello@alexbilbie.com",
+                    "homepage": "http://www.alexbilbie.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Woody Gilk",
+                    "homepage": "https://github.com/shadowhand",
+                    "role": "Contributor"
+                }
+            ],
+            "description": "OAuth 2.0 Client Library",
+            "keywords": [
+                "Authentication",
+                "SSO",
+                "authorization",
+                "identity",
+                "idp",
+                "oauth",
+                "oauth2",
+                "single sign on"
+            ],
+            "support": {
+                "issues": "https://github.com/thephpleague/oauth2-client/issues",
+                "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0"
+            },
+            "time": "2023-04-16T18:19:15+00:00"
+        },
         {
             "name": "league/oauth2-server",
             "version": "8.5.4",
@@ -3382,6 +3777,50 @@
             },
             "time": "2024-04-02T15:57:53+00:00"
         },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
         {
             "name": "scssphp/scssphp",
             "version": "v1.12.1",
@@ -8217,5 +8656,5 @@
     "platform-overrides": {
         "php": "8.1"
     },
-    "plugin-api-version": "2.2.0"
+    "plugin-api-version": "2.6.0"
 }
diff --git a/config/config_defaults.inc.php b/config/config_defaults.inc.php
index 78d53569a9f..ce484922be6 100644
--- a/config/config_defaults.inc.php
+++ b/config/config_defaults.inc.php
@@ -245,13 +245,14 @@ $_language_domain = "studip";  // the name of the language file. Should not be c
 ----------------------------------------------------------------
 the following plugins are available:
 Standard        authentication using the local Stud.IP database
-StandardExtern      authentication using an alternative Stud.IP database, e.g. another installation
+StandardExtern  authentication using an alternative Stud.IP database, e.g. another installation
 Ldap            authentication using an LDAP server, this plugin uses anonymous bind against LDAP to retrieve the user dn,
-            then it uses the submitted password to authenticate with this user dn
+                then it uses the submitted password to authenticate with this user dn
 LdapReader      authentication using an LDAP server, this plugin binds to the server using a given dn and a password,
-            this account must have read access to gather the attributes for the user who tries to authenticate.
-CAS         authentication using a central authentication server (CAS)
+                this account must have read access to gather the attributes for the user who tries to authenticate.
+CAS             authentication using a central authentication server (CAS)
 Shib            authentication using a Shibboleth identity provider (IdP)
+OAuth2          authentication using an OAuth2 identity provider
 
 If you write your own plugin put it in studip-htdocs/lib/classes/auth_plugins
 and enable it here. The name of the plugin is the classname excluding "StudipAuth".
@@ -267,6 +268,7 @@ $STUDIP_AUTH_PLUGIN[] = "Standard";
 // $STUDIP_AUTH_PLUGIN[] = "LTI";
 // $STUDIP_AUTH_PLUGIN[] = "Shib";
 // $STUDIP_AUTH_PLUGIN[] = "IP";
+// $STUDIP_AUTH_PLUGIN[] = 'OAuth2';
 
 $STUDIP_AUTH_CONFIG_STANDARD = ["error_head" => "intern"];
 
@@ -378,6 +380,26 @@ $STUDIP_AUTH_CONFIG_SHIB = array("session_initiator" => "https://sp.studip.de/Sh
 
 $STUDIP_AUTH_CONFIG_IP = array('allowed_users' =>
     array ('root' => array('127.0.0.1', '::1')));
+
+$STUDIP_AUTH_CONFIG_OAUTH2 = [
+    'client_id'                  => '',
+    'client_secret'              => '',
+    'redirect_uri'               => '',
+
+    'url_authorize'              => '',
+    'url_access_token'           => '',
+    'url_resource_owner_details' => '',
+
+    'login_description' => 'Login with OAuth2',
+
+    'user_data_mapping' => [
+        'auth_user_md5.username' => ['callback' => 'getUserData', 'map_args' => 'nickname'],
+        'auth_user_md5.password' => ['callback' => 'dummy', 'map_args' => ''],
+        'auth_user_md5.Vorname'  => ['callback' => 'getUserData', 'map_args' => 'given_name'],
+        'auth_user_md5.Nachname' => ['callback' => 'getUserData', 'map_args' => 'family_name'],
+        'auth_user_md5.EMail'    => ['callback' => 'getUserData', 'map_args' => 'email'],
+    ],
+];
 */
 
 //some additional authification-settings
diff --git a/lib/classes/auth_plugins/StudipAuthOAuth2.php b/lib/classes/auth_plugins/StudipAuthOAuth2.php
new file mode 100644
index 00000000000..aa9077633e8
--- /dev/null
+++ b/lib/classes/auth_plugins/StudipAuthOAuth2.php
@@ -0,0 +1,113 @@
+<?php
+use League\OAuth2\Client\Provider\GenericProvider;
+
+/**
+ * StudipAuthOAuth2.php - Stud.IP authentication using OAuth2
+ *
+ * @copyright 2024 Jan-Hendrik Willms <tleilax@gmail.com>
+ * @license GPL2 or any later version
+ * @since Stud.IP 6.0
+ */
+final class StudipAuthOAuth2 extends StudipAuthSSO
+{
+    protected string $client_id;
+    protected string $client_secret;
+    protected string $redirect_uri;
+
+    protected string $url_authorize;
+    protected string $url_access_token;
+    protected string $url_resource_owner_details;
+
+    private GenericProvider $oauth2_provider;
+
+    private ?array $user_data = null;
+
+    public function __construct($config = [])
+    {
+        parent::__construct($config);
+
+        if (!isset($this->plugin_fullname)) {
+            $this->plugin_fullname = _('OAuth2');
+        }
+
+        if (Request::option('sso') === $this->plugin_name) {
+            $options = [
+                'clientId' => $this->client_id,
+                'clientSecret' => $this->client_secret,
+                'redirectUri' => $this->redirect_uri,
+                'urlAuthorize' => $this->url_authorize,
+                'urlAccessToken' => $this->url_access_token,
+                'urlResourceOwnerDetails' => $this->url_resource_owner_details,
+            ];
+
+            if (Config::get()->getValue('HTTP_PROXY')) {
+                $options['proxy'] = Config::get()->getValue('HTTP_PROXY');
+                $options['verify'] = false;
+            }
+
+            $this->oauth2_provider = new GenericProvider($options);
+        }
+    }
+
+    public function getUser()
+    {
+        return $this->getUserData($this->getUsernameKey());
+    }
+
+    public function verifyUsername($username)
+    {
+        if (isset($this->user_data)) {
+            return parent::verifyUsername($this->getUser());
+        }
+
+        if (!Request::get('code')) {
+            $authorizationUrl = $this->oauth2_provider->getAuthorizationUrl(['scope' => 'profile email']);
+
+            $_SESSION[self::class] = [
+                'state' => $this->oauth2_provider->getState(),
+                'redirect' => Request::url(),
+            ];
+
+            page_close();
+            header('Location: ' . $authorizationUrl);
+            die;
+        } elseif (
+            !Request::get('state')
+            || empty($_SESSION[self::class]['state'])
+            || Request::get('state') !== $_SESSION[self::class]['state']
+        ) {
+            if (isset($_SESSION[self::class])) {
+                unset($_SESSION[self::class]);
+            }
+        } else {
+            $accessToken = $this->oauth2_provider->getAccessToken('authorization_code', [
+                'code' => Request::get('code'),
+            ]);
+
+            $resourceOwner = $this->oauth2_provider->getResourceOwner($accessToken);
+
+            $this->user_data = $resourceOwner->toArray();
+
+            return parent::verifyUsername($this->getUser());
+        }
+
+        return null;
+    }
+
+    /**
+     * Callback that can be used in user_data_mapping array.
+     */
+    public function getUserData(string $key): ?string
+    {
+        return $this->user_data[$key];
+    }
+
+    /**
+     * Returns the key used to store the username from user_data_mapping if
+     * present. Defaults to 'nickname'.
+     */
+    private function getUsernameKey(): string
+    {
+        return $this->user_data_mapping['map_args']['auth_user_md5.username'] ?? 'nickname';
+    }
+}
diff --git a/lib/phplib/Seminar_Auth.php b/lib/phplib/Seminar_Auth.php
index 92ee2c1f64d..546d6d83937 100644
--- a/lib/phplib/Seminar_Auth.php
+++ b/lib/phplib/Seminar_Auth.php
@@ -125,7 +125,13 @@ class Seminar_Auth
                 # Check for user supplied automatic login procedure
                 if ($uid = $this->auth_preauth()) {
                     $this->auth["uid"] = $uid;
-                    $sess->regenerate_session_id(['auth', '_language', 'phpCAS', 'contrast']);
+                    $sess->regenerate_session_id([
+                        '_language',
+                        'auth',
+                        'contrast',
+                        'phpCAS',
+                        StudipAuthOAuth2::class
+                    ]);
                     $sess->freeze();
                     $GLOBALS['user'] = new Seminar_User($this->auth['uid']);
                     return true;
diff --git a/lib/seminar_open.php b/lib/seminar_open.php
index 2e831bf7ce1..7e3acf61e82 100644
--- a/lib/seminar_open.php
+++ b/lib/seminar_open.php
@@ -158,6 +158,15 @@ if (Navigation::hasItem('/profile/edit')) {
 }
 
 if ($user_did_login) {
+    if (isset($_SESSION[StudipAuthOAuth2::class]['redirect'])) {
+        $redirect = $_SESSION[StudipAuthOAuth2::class]['redirect'];
+        unset($_SESSION[StudipAuthOAuth2::class]);
+
+        page_close();
+        header('Location: ' . $redirect);
+        die;
+    }
+
     NotificationCenter::postNotification('UserDidLogin', $user->id);
 }
 
-- 
GitLab