From 8b2a995f7c725488bd41ddb389cd01060dd53ecc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 7 Jan 2021 12:43:24 +0100 Subject: [PATCH] Implement the OAuth 2 providers using AbstractOauth2Action. --- .../templates/accountManagement.tpl | 6 +- .../lib/action/FacebookAuthAction.class.php | 226 ++++++++-------- .../lib/action/GithubAuthAction.class.php | 252 +++++++++--------- .../lib/action/GoogleAuthAction.class.php | 240 +++++++++-------- .../lib/action/TwitterAuthAction.class.php | 2 +- .../lib/form/AccountManagementForm.class.php | 41 +-- .../files/lib/form/RegisterForm.class.php | 55 +--- wcfsetup/install/lang/de.xml | 12 +- wcfsetup/install/lang/en.xml | 12 +- 9 files changed, 421 insertions(+), 425 deletions(-) diff --git a/com.woltlab.wcf/templates/accountManagement.tpl b/com.woltlab.wcf/templates/accountManagement.tpl index 3791d2fa54..6bbfb785ff 100644 --- a/com.woltlab.wcf/templates/accountManagement.tpl +++ b/com.woltlab.wcf/templates/accountManagement.tpl @@ -209,7 +209,7 @@
{lang}wcf.user.3rdparty.github{/lang}
- {if $__wcf->getSession()->getVar('__githubToken')} + {if $__wcf->session->getVar('__3rdPartyProvider') === 'github' && $__wcf->session->getVar('__oauthUser')} {else} {lang}wcf.user.3rdparty.github.connect{/lang} @@ -235,7 +235,7 @@
{lang}wcf.user.3rdparty.facebook{/lang}
- {if $__wcf->getSession()->getVar('__facebookData')} + {if $__wcf->session->getVar('__3rdPartyProvider') === 'facebook' && $__wcf->session->getVar('__oauthUser')} {else} {lang}wcf.user.3rdparty.facebook.connect{/lang} @@ -248,7 +248,7 @@
{lang}wcf.user.3rdparty.google{/lang}
- {if $__wcf->getSession()->getVar('__googleData')} + {if $__wcf->session->getVar('__3rdPartyProvider') === 'google' && $__wcf->session->getVar('__oauthUser')} {else} {lang}wcf.user.3rdparty.google.connect{/lang} diff --git a/wcfsetup/install/files/lib/action/FacebookAuthAction.class.php b/wcfsetup/install/files/lib/action/FacebookAuthAction.class.php index d86b8c775d..6dc0ae184d 100644 --- a/wcfsetup/install/files/lib/action/FacebookAuthAction.class.php +++ b/wcfsetup/install/files/lib/action/FacebookAuthAction.class.php @@ -1,28 +1,25 @@ * @package WoltLabSuite\Core\Action */ -class FacebookAuthAction extends AbstractAction { +final class FacebookAuthAction extends AbstractOauth2Action { /** * @inheritDoc */ @@ -31,125 +28,132 @@ class FacebookAuthAction extends AbstractAction { /** * @inheritDoc */ - public function readParameters() { - parent::readParameters(); - - if (WCF::getSession()->spiderID) { - throw new IllegalLinkException(); - } + protected function getTokenEndpoint(): string { + return 'https://graph.facebook.com/oauth/access_token'; + } + + /** + * @inheritDoc + */ + protected function getClientId(): string { + return StringUtil::trim(FACEBOOK_PUBLIC_KEY); + } + + /** + * @inheritDoc + */ + protected function getClientSecret(): string { + return StringUtil::trim(FACEBOOK_PRIVATE_KEY); + } + + /** + * @inheritDoc + */ + protected function getScope(): string { + return 'email'; + } + + /** + * @inheritDoc + */ + protected function getAuthorizeUrl(): string { + return 'https://www.facebook.com/dialog/oauth'; } /** * @inheritDoc */ - public function execute() { - parent::execute(); + protected function getCallbackUrl(): string { + $callbackURL = LinkHandler::getInstance()->getControllerLink(self::class); - $callbackURL = LinkHandler::getInstance()->getLink('FacebookAuth'); - // Work around Facebook performing an illegal substitution of the Slash // by '%2F' when entering redirect URI (RFC 3986 sect. 2.2, sect. 3.4) - $callbackURL = preg_replace_callback('/(?<=\?).*/', function ($matches) { - return rawurlencode($matches[0]); + $callbackURL = \preg_replace_callback('/(?<=\?).*/', function ($matches) { + return \rawurlencode($matches[0]); }, $callbackURL); - - // user accepted the connection - if (isset($_GET['code'])) { - try { - // fetch access_token - $request = new HTTPRequest('https://graph.facebook.com/oauth/access_token?client_id='.StringUtil::trim(FACEBOOK_PUBLIC_KEY).'&redirect_uri='.rawurlencode($callbackURL).'&client_secret='.StringUtil::trim(FACEBOOK_PRIVATE_KEY).'&code='.rawurlencode($_GET['code'])); - $request->execute(); - $reply = $request->getReply(); + + return $callbackURL; + } + + /** + * @inheritDoc + */ + protected function supportsState(): bool { + return true; + } + + /** + * @inheritDoc + */ + protected function getUser(array $accessToken): OauthUser { + $request = new Request('GET', 'https://graph.facebook.com/me?fields=email,id,name', [ + 'accept' => 'application/json', + 'authorization' => \sprintf('Bearer %s', $accessToken['access_token']), + ]); + $response = $this->getHttpClient()->send($request); + $parsed = JSON::decode((string) $response->getBody()); + + $parsed['__id'] = $parsed['id']; + $parsed['__username'] = $parsed['name']; + $parsed['__email'] = $parsed['email']; + $parsed['accessToken'] = $accessToken; + + return new OauthUser($parsed); + } + + /** + * @inheritDoc + */ + protected function processUser(OauthUser $oauthUser) { + $user = User::getUserByAuthData('facebook:'.$oauthUser->getId()); + + if ($user->userID) { + if (WCF::getUser()->userID) { + // This account belongs to an existing user, but we are already logged in. + // This can't be handled. - $content = $reply['body']; - } - catch (SystemException $e) { - \wcf\functions\exception\logThrowable($e); - throw new IllegalLinkException(); - } - - // validate state, validation of state is executed after fetching the access_token to invalidate 'code' - if (!isset($_GET['state']) || !WCF::getSession()->getVar('__facebookInit') || !\hash_equals(WCF::getSession()->getVar('__facebookInit'), $_GET['state'])) throw new IllegalLinkException(); - WCF::getSession()->unregister('__facebookInit'); - - try { - $data = JSON::decode($content); - } - catch (SystemException $e) { - parse_str($content, $data); + throw new NamedUserException( + WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.facebook.connect.error.inuse') + ); } - - if (!isset($data['access_token'])) throw new IllegalLinkException(); - - try { - // fetch userdata - $request = new HTTPRequest('https://graph.facebook.com/me?access_token='.rawurlencode($data['access_token']).'&fields=email,id,name'); - $request->execute(); - $reply = $request->getReply(); + else { + // This account belongs to an existing user, we are not logged in. + // Perform the login. - $content = $reply['body']; - } - catch (SystemException $e) { - \wcf\functions\exception\logThrowable($e); - throw new IllegalLinkException(); + WCF::getSession()->changeUser($user); + WCF::getSession()->update(); + HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); + exit; } + } + else { + WCF::getSession()->register('__3rdPartyProvider', 'facebook'); - $userData = JSON::decode($content); - - // check whether a user is connected to this facebook account - $user = User::getUserByAuthData('facebook:'.$userData['id']); - - if ($user->userID) { - // a user is already connected, but we are logged in, break - if (WCF::getUser()->userID) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.facebook.connect.error.inuse')); - } - // perform login - else { - WCF::getSession()->changeUser($user); - WCF::getSession()->update(); - HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); - } + if (WCF::getUser()->userID) { + // This account does not belong to anyone and we are already logged in. + // Thus we want to connect this account. + + WCF::getSession()->register('__oauthUser', $oauthUser); + + HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); + exit; } else { - WCF::getSession()->register('__3rdPartyProvider', 'facebook'); - // save data for connection - if (WCF::getUser()->userID) { - WCF::getSession()->register('__facebookUsername', $userData['name']); - WCF::getSession()->register('__facebookData', $userData); - - HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); - } - // save data and redirect to registration - else { - WCF::getSession()->register('__username', $userData['name']); - if (isset($userData['email'])) WCF::getSession()->register('__email', $userData['email']); - WCF::getSession()->register('__facebookData', $userData); - - // we assume that bots won't register on facebook first - // thus no need for a captcha - if (REGISTER_USE_CAPTCHA) { - WCF::getSession()->register('noRegistrationCaptcha', true); - } - - WCF::getSession()->update(); - HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register')); - } + // This account does not belong to anyone and we are not logged in. + // Thus we want to connect this account to a newly registered user. + + WCF::getSession()->register('__oauthUser', $oauthUser); + WCF::getSession()->register('__username', $oauthUser->getUsername()); + WCF::getSession()->register('__email', $oauthUser->getEmail()); + + // We assume that bots won't register an external account first, so + // we skip the captcha. + WCF::getSession()->register('noRegistrationCaptcha', true); + + WCF::getSession()->update(); + HeaderUtil::redirect(LinkHandler::getInstance()->getControllerLink(RegisterForm::class)); + exit; } - - $this->executed(); - exit; - } - // user declined or any other error that may occur - if (isset($_GET['error'])) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.facebook.login.error.'.$_GET['error'])); } - - // start auth by redirecting to facebook - $token = Hex::encode(\random_bytes(20)); - WCF::getSession()->register('__facebookInit', $token); - HeaderUtil::redirect("https://www.facebook.com/dialog/oauth?client_id=".StringUtil::trim(FACEBOOK_PUBLIC_KEY). "&redirect_uri=".rawurlencode($callbackURL)."&state=".$token."&scope=email"); - $this->executed(); - exit; } } diff --git a/wcfsetup/install/files/lib/action/GithubAuthAction.class.php b/wcfsetup/install/files/lib/action/GithubAuthAction.class.php index 873f3b839a..bd55fa7400 100644 --- a/wcfsetup/install/files/lib/action/GithubAuthAction.class.php +++ b/wcfsetup/install/files/lib/action/GithubAuthAction.class.php @@ -1,28 +1,26 @@ * @package WoltLabSuite\Core\Action */ -class GithubAuthAction extends AbstractAction { +final class GithubAuthAction extends AbstractOauth2Action { /** * @inheritDoc */ @@ -31,140 +29,144 @@ class GithubAuthAction extends AbstractAction { /** * @inheritDoc */ - public function readParameters() { - parent::readParameters(); + protected function getTokenEndpoint(): string { + return 'https://github.com/login/oauth/access_token'; + } + + /** + * @inheritDoc + */ + protected function getClientId(): string { + return StringUtil::trim(GITHUB_PUBLIC_KEY); + } + + /** + * @inheritDoc + */ + protected function getClientSecret(): string { + return StringUtil::trim(GITHUB_PRIVATE_KEY); + } + + /** + * @inheritDoc + */ + protected function getScope(): string { + return 'user:email'; + } + + /** + * @inheritDoc + */ + protected function getAuthorizeUrl(): string { + return 'https://github.com/login/oauth/authorize'; + } + + /** + * @inheritDoc + */ + protected function getCallbackUrl(): string { + return LinkHandler::getInstance()->getControllerLink(self::class); + } + + /** + * @inheritDoc + */ + protected function supportsState(): bool { + return true; + } + + /** + * @inheritDoc + */ + protected function getUser(array $accessToken): OauthUser { + $request = new Request('GET', 'https://api.github.com/user', [ + 'accept' => 'application/json', + 'authorization' => \sprintf('Bearer %s', $accessToken['access_token']), + ]); + $response = $this->getHttpClient()->send($request); + $parsed = JSON::decode((string) $response->getBody()); - if (WCF::getSession()->spiderID) { - throw new IllegalLinkException(); - } + $parsed['__id'] = $parsed['id']; + $parsed['__username'] = $parsed['login']; + $parsed['accessToken'] = $accessToken; + + return new OauthUser($parsed); } /** * @inheritDoc */ - public function execute() { - parent::execute(); + protected function processUser(OauthUser $oauthUser) { + $user = User::getUserByAuthData('github:'.$oauthUser->getId()); - // user accepted the connection - if (isset($_GET['code'])) { - try { - // fetch access_token - $request = new HTTPRequest('https://github.com/login/oauth/access_token', [], [ - 'client_id' => StringUtil::trim(GITHUB_PUBLIC_KEY), - 'client_secret' => StringUtil::trim(GITHUB_PRIVATE_KEY), - 'code' => $_GET['code'] - ]); - $request->execute(); - $reply = $request->getReply(); + if ($user->userID) { + if (WCF::getUser()->userID) { + // This account belongs to an existing user, but we are already logged in. + // This can't be handled. - $content = $reply['body']; - } - catch (SystemException $e) { - \wcf\functions\exception\logThrowable($e); - throw new IllegalLinkException(); + throw new NamedUserException( + WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.github.connect.error.inuse') + ); } - - // validate state, validation of state is executed after fetching the access_token to invalidate 'code' - if (!isset($_GET['state']) || !WCF::getSession()->getVar('__githubInit') || !\hash_equals(WCF::getSession()->getVar('__githubInit'), $_GET['state'])) throw new IllegalLinkException(); - WCF::getSession()->unregister('__githubInit'); - - parse_str($content, $data); - - // check whether the token is okay - if (isset($data['error'])) throw new IllegalLinkException(); - - try { - // fetch userdata - $request = new HTTPRequest('https://api.github.com/user'); - $request->addHeader('Authorization', 'token '.$data['access_token']); - $request->execute(); - $reply = $request->getReply(); - $userData = JSON::decode(StringUtil::trim($reply['body'])); - } - catch (SystemException $e) { - \wcf\functions\exception\logThrowable($e); - throw new IllegalLinkException(); - } - - // check whether a user is connected to this github account - $user = User::getUserByAuthData('github:'.$userData['id']); - if (!$user->userID) { - $user = User::getUserByAuthData('github:'.$data['access_token']); - $userEditor = new UserEditor($user); - $userEditor->update(['authData' => 'github:'.$userData['id']]); + else { + // This account belongs to an existing user, we are not logged in. + // Perform the login. + + WCF::getSession()->changeUser($user); + WCF::getSession()->update(); + HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); + exit; } + } + else { + WCF::getSession()->register('__3rdPartyProvider', 'github'); - if ($user->userID) { - // a user is already connected, but we are logged in, break - if (WCF::getUser()->userID) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.github.connect.error.inuse')); - } - // perform login - else { - WCF::getSession()->changeUser($user); - WCF::getSession()->update(); - HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); - } + if (WCF::getUser()->userID) { + // This account does not belong to anyone and we are already logged in. + // Thus we want to connect this account. + + WCF::getSession()->register('__oauthUser', $oauthUser); + + HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); + exit; } else { - WCF::getSession()->register('__3rdPartyProvider', 'github'); - // save data for connection - if (WCF::getUser()->userID) { - WCF::getSession()->register('__githubUsername', $userData['login']); - WCF::getSession()->register('__githubToken', $data['access_token']); - - HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); - } - // save data and redirect to registration - else { - WCF::getSession()->register('__githubData', $userData); - WCF::getSession()->register('__username', $userData['login']); + // This account does not belong to anyone and we are not logged in. + // Thus we want to connect this account to a newly registered user. + + try { + $request = new Request('GET', 'https://api.github.com/user/emails', [ + 'accept' => 'application/json', + 'authorization' => \sprintf('Bearer %s', $oauthUser["accessToken"]["access_token"]), + ]); + $response = $this->getHttpClient()->send($request); + $emails = JSON::decode((string) $response->getBody()); - try { - $request = new HTTPRequest('https://api.github.com/user/emails'); - $request->addHeader('Authorization', 'token '.$data['access_token']); - $request->execute(); - $reply = $request->getReply(); - $emails = JSON::decode(StringUtil::trim($reply['body'])); - - // search primary email - $email = $emails[0]['email']; - foreach ($emails as $tmp) { - if ($tmp['primary']) { - $email = $tmp['email']; - break; - } + // search primary email + $email = $emails[0]['email']; + foreach ($emails as $tmp) { + if ($tmp['primary']) { + $email = $tmp['email']; + break; } - WCF::getSession()->register('__email', $email); - } - catch (SystemException $e) { - } - WCF::getSession()->register('__githubToken', $data['access_token']); - - // we assume that bots won't register on github first - // thus no need for a captcha - if (REGISTER_USE_CAPTCHA) { - WCF::getSession()->register('noRegistrationCaptcha', true); } - - WCF::getSession()->update(); - HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register')); + $oauthUser["__email"] = $email; + } + catch (GuzzleException $e) { } + + WCF::getSession()->register('__oauthUser', $oauthUser); + WCF::getSession()->register('__username', $oauthUser->getUsername()); + WCF::getSession()->register('__email', $oauthUser->getEmail()); + + // We assume that bots won't register an external account first, so + // we skip the captcha. + WCF::getSession()->register('noRegistrationCaptcha', true); + + WCF::getSession()->update(); + HeaderUtil::redirect(LinkHandler::getInstance()->getControllerLink(RegisterForm::class)); + exit; } - - $this->executed(); - exit; } - // user declined or any other error that may occur - if (isset($_GET['error'])) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.github.login.error.'.$_GET['error'])); - } - - // start auth by redirecting to github - $token = Hex::encode(\random_bytes(20)); - WCF::getSession()->register('__githubInit', $token); - HeaderUtil::redirect("https://github.com/login/oauth/authorize?client_id=".rawurlencode(StringUtil::trim(GITHUB_PUBLIC_KEY))."&scope=".rawurlencode('user:email')."&state=".$token); - $this->executed(); - exit; } } diff --git a/wcfsetup/install/files/lib/action/GoogleAuthAction.class.php b/wcfsetup/install/files/lib/action/GoogleAuthAction.class.php index 57159330f8..910b562e1c 100644 --- a/wcfsetup/install/files/lib/action/GoogleAuthAction.class.php +++ b/wcfsetup/install/files/lib/action/GoogleAuthAction.class.php @@ -1,158 +1,172 @@ * @package WoltLabSuite\Core\Action */ -class GoogleAuthAction extends AbstractAction { +final class GoogleAuthAction extends AbstractOauth2Action { /** * @inheritDoc */ public $neededModules = ['GOOGLE_PUBLIC_KEY', 'GOOGLE_PRIVATE_KEY']; + /** + * @var array + */ + private $configuration; + + /** + * Returns Google's OpenID Connect configuration. + */ + private function getConfiguration() { + if (!$this->configuration) { + $request = new Request('GET', 'https://accounts.google.com/.well-known/openid-configuration'); + $response = $this->getHttpClient()->send($request); + + $this->configuration = JSON::decode((string) $response->getBody()); + } + + return $this->configuration; + } + + /** + * @inheritDoc + */ + protected function getTokenEndpoint(): string { + return $this->getConfiguration()['token_endpoint']; + } + /** * @inheritDoc */ - public function readParameters() { - parent::readParameters(); + protected function getClientId(): string { + return StringUtil::trim(GOOGLE_PUBLIC_KEY); + } + + /** + * @inheritDoc + */ + protected function getClientSecret(): string { + return StringUtil::trim(GOOGLE_PRIVATE_KEY); + } + + /** + * @inheritDoc + */ + protected function getScope(): string { + return 'profile openid email'; + } + + /** + * @inheritDoc + */ + protected function getAuthorizeUrl(): string { + return $this->getConfiguration()['authorization_endpoint']; + } + + /** + * @inheritDoc + */ + protected function getCallbackUrl(): string { + return LinkHandler::getInstance()->getControllerLink(self::class); + } + + /** + * @inheritDoc + */ + protected function supportsState(): bool { + return true; + } + + /** + * @inheritDoc + */ + protected function getUser(array $accessToken): OauthUser { + $request = new Request('GET', $this->getConfiguration()['userinfo_endpoint'], [ + 'accept' => 'application/json', + 'authorization' => \sprintf('Bearer %s', $accessToken['access_token']), + ]); + $response = $this->getHttpClient()->send($request); + $parsed = JSON::decode((string) $response->getBody()); - if (WCF::getSession()->spiderID) { - throw new IllegalLinkException(); + $parsed['__id'] = $parsed['sub']; + $parsed['__username'] = $parsed['name']; + if ($parsed['email']) { + $parsed['__email'] = $parsed['email']; } + $parsed['accessToken'] = $accessToken; + + return new OauthUser($parsed); } /** * @inheritDoc */ - public function execute() { - parent::execute(); + protected function processUser(OauthUser $oauthUser) { + $user = User::getUserByAuthData('google:'.$oauthUser->getId()); - $callbackURL = LinkHandler::getInstance()->getLink('GoogleAuth'); - // user accepted the connection - if (isset($_GET['code'])) { - try { - // fetch access_token - $request = new HTTPRequest('https://accounts.google.com/o/oauth2/token', [], [ - 'code' => $_GET['code'], - 'client_id' => StringUtil::trim(GOOGLE_PUBLIC_KEY), - 'client_secret' => StringUtil::trim(GOOGLE_PRIVATE_KEY), - 'redirect_uri' => $callbackURL, - 'grant_type' => 'authorization_code' - ]); - $request->execute(); - $reply = $request->getReply(); + if ($user->userID) { + if (WCF::getUser()->userID) { + // This account belongs to an existing user, but we are already logged in. + // This can't be handled. - $content = $reply['body']; + throw new NamedUserException( + WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.google.connect.error.inuse') + ); } - catch (SystemException $e) { - \wcf\functions\exception\logThrowable($e); - throw new IllegalLinkException(); + else { + // This account belongs to an existing user, we are not logged in. + // Perform the login. + + WCF::getSession()->changeUser($user); + WCF::getSession()->update(); + HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); + exit; } + } + else { + WCF::getSession()->register('__3rdPartyProvider', 'google'); - // validate state, validation of state is executed after fetching the access_token to invalidate 'code' - if (!isset($_GET['state']) || !WCF::getSession()->getVar('__googleInit') || !\hash_equals(WCF::getSession()->getVar('__googleInit'), $_GET['state'])) throw new IllegalLinkException(); - WCF::getSession()->unregister('__googleInit'); - - $data = JSON::decode($content); - - try { - // fetch openID connect configuration - $request = new HTTPRequest('https://accounts.google.com/.well-known/openid-configuration'); - $request->execute(); - $reply = $request->getReply(); - $content = JSON::decode($reply['body']); + if (WCF::getUser()->userID) { + // This account does not belong to anyone and we are already logged in. + // Thus we want to connect this account. - // fetch userdata - $request = new HTTPRequest($content['userinfo_endpoint']); - $request->addHeader('Authorization', 'Bearer '.$data['access_token']); - $request->execute(); - $reply = $request->getReply(); + WCF::getSession()->register('__oauthUser', $oauthUser); - $content = $reply['body']; - } - catch (SystemException $e) { - \wcf\functions\exception\logThrowable($e); - throw new IllegalLinkException(); - } - - $userData = JSON::decode($content); - - // check whether a user is connected to this google account - $user = User::getUserByAuthData('google:'.$userData['sub']); - - if ($user->userID) { - // a user is already connected, but we are logged in, break - if (WCF::getUser()->userID) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.google.connect.error.inuse')); - } - // perform login - else { - WCF::getSession()->changeUser($user); - WCF::getSession()->update(); - HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); - } + HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); + exit; } else { - WCF::getSession()->register('__3rdPartyProvider', 'google'); + // This account does not belong to anyone and we are not logged in. + // Thus we want to connect this account to a newly registered user. + + WCF::getSession()->register('__oauthUser', $oauthUser); + WCF::getSession()->register('__username', $oauthUser->getUsername()); + WCF::getSession()->register('__email', $oauthUser->getEmail()); + + // We assume that bots won't register an external account first, so + // we skip the captcha. + WCF::getSession()->register('noRegistrationCaptcha', true); - // save data for connection - if (WCF::getUser()->userID) { - WCF::getSession()->register('__googleUsername', $userData['name']); - WCF::getSession()->register('__googleData', $userData); - - HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); - } - // save data and redirect to registration - else { - WCF::getSession()->register('__username', $userData['name']); - if (isset($userData['email'])) { - WCF::getSession()->register('__email', $userData['email']); - } - - WCF::getSession()->register('__googleData', $userData); - - // we assume that bots won't register on google first - // thus no need for a captcha - if (REGISTER_USE_CAPTCHA) { - WCF::getSession()->register('noRegistrationCaptcha', true); - } - - WCF::getSession()->update(); - HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register')); - } + WCF::getSession()->update(); + HeaderUtil::redirect(LinkHandler::getInstance()->getControllerLink(RegisterForm::class)); + exit; } - - $this->executed(); - exit; - } - // user declined or any other error that may occur - if (isset($_GET['error'])) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.google.login.error.'.$_GET['error'])); } - - // start auth by redirecting to google - $token = Hex::encode(\random_bytes(20)); - WCF::getSession()->register('__googleInit', $token); - HeaderUtil::redirect("https://accounts.google.com/o/oauth2/auth?client_id=".rawurlencode(StringUtil::trim(GOOGLE_PUBLIC_KEY)). "&redirect_uri=".rawurlencode($callbackURL)."&state=".$token."&scope=profile+openid+email&response_type=code"); - $this->executed(); - exit; } } diff --git a/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php b/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php index 59c84735a6..8c1d69d050 100644 --- a/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php +++ b/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php @@ -159,7 +159,7 @@ class TwitterAuthAction extends AbstractAction { // user declined if (isset($_GET['denied'])) { - throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.twitter.login.error.denied')); + throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.login.error.denied')); } // start auth by fetching request_token diff --git a/wcfsetup/install/files/lib/form/AccountManagementForm.class.php b/wcfsetup/install/files/lib/form/AccountManagementForm.class.php index 4df48e8fa8..7dadf52dc9 100644 --- a/wcfsetup/install/files/lib/form/AccountManagementForm.class.php +++ b/wcfsetup/install/files/lib/form/AccountManagementForm.class.php @@ -378,13 +378,16 @@ class AccountManagementForm extends AbstractForm { // 3rdParty if (GITHUB_PUBLIC_KEY !== '' && GITHUB_PRIVATE_KEY !== '') { - if ($this->githubConnect && WCF::getSession()->getVar('__githubData')) { - $githubData = WCF::getSession()->getVar('__githubData'); - $updateParameters['authData'] = 'github:'.$githubData['id']; + if ( $this->githubConnect && + WCF::getSession()->getVar('__3rdPartyProvider') == 'github' && + ($oauthUser = WCF::getSession()->getVar('__oauthUser')) + ) { + $updateParameters['authData'] = 'github:'.$oauthUser->getId(); + $updateParameters['password'] = null; $success[] = 'wcf.user.3rdparty.github.connect.success'; - WCF::getSession()->unregister('__githubToken'); - WCF::getSession()->unregister('__githubUsername'); + WCF::getSession()->unregister('__3rdPartyProvider'); + WCF::getSession()->unregister('__oauthUser'); } } if ($this->githubDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'github:')) { @@ -406,13 +409,17 @@ class AccountManagementForm extends AbstractForm { $success[] = 'wcf.user.3rdparty.twitter.disconnect.success'; } if (FACEBOOK_PUBLIC_KEY !== '' && FACEBOOK_PRIVATE_KEY !== '') { - if ($this->facebookConnect && WCF::getSession()->getVar('__facebookData')) { - $facebookData = WCF::getSession()->getVar('__facebookData'); - $updateParameters['authData'] = 'facebook:'.$facebookData['id']; + if ( + $this->facebookConnect && + WCF::getSession()->getVar('__3rdPartyProvider') == 'facebook' && + ($oauthUser = WCF::getSession()->getVar('__oauthUser')) + ) { + $updateParameters['authData'] = 'facebook:'.$oauthUser->getId(); + $updateParameters['password'] = null; $success[] = 'wcf.user.3rdparty.facebook.connect.success'; - WCF::getSession()->unregister('__facebookData'); - WCF::getSession()->unregister('__facebookUsername'); + WCF::getSession()->unregister('__3rdPartyProvider'); + WCF::getSession()->unregister('__oauthUser'); } } if ($this->facebookDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'facebook:')) { @@ -420,13 +427,17 @@ class AccountManagementForm extends AbstractForm { $success[] = 'wcf.user.3rdparty.facebook.disconnect.success'; } if (GOOGLE_PUBLIC_KEY !== '' && GOOGLE_PRIVATE_KEY !== '') { - if ($this->googleConnect && WCF::getSession()->getVar('__googleData')) { - $googleData = WCF::getSession()->getVar('__googleData'); - $updateParameters['authData'] = 'google:'.$googleData['sub']; + if ( + $this->googleConnect && + WCF::getSession()->getVar('__3rdPartyProvider') == 'google' && + ($oauthUser = WCF::getSession()->getVar('__oauthUser')) + ) { + $updateParameters['authData'] = 'google:'.$oauthUser->getId(); + $updateParameters['password'] = null; $success[] = 'wcf.user.3rdparty.google.connect.success'; - WCF::getSession()->unregister('__googleData'); - WCF::getSession()->unregister('__googleUsername'); + WCF::getSession()->unregister('__3rdPartyProvider'); + WCF::getSession()->unregister('__oauthUser'); } } if ($this->googleDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'google:')) { diff --git a/wcfsetup/install/files/lib/form/RegisterForm.class.php b/wcfsetup/install/files/lib/form/RegisterForm.class.php index 3414c25ed5..f7b79165fc 100644 --- a/wcfsetup/install/files/lib/form/RegisterForm.class.php +++ b/wcfsetup/install/files/lib/form/RegisterForm.class.php @@ -219,11 +219,9 @@ class RegisterForm extends UserAddForm { if (WCF::getSession()->getVar('__username')) { $this->username = WCF::getSession()->getVar('__username'); - WCF::getSession()->unregister('__username'); } if (WCF::getSession()->getVar('__email')) { $this->email = $this->confirmEmail = WCF::getSession()->getVar('__email'); - WCF::getSession()->unregister('__email'); } WCF::getSession()->register('registrationStartTime', TIME_NOW); @@ -331,27 +329,20 @@ class RegisterForm extends UserAddForm { $registerVia3rdParty = false; if ($this->isExternalAuthentication) { - switch (WCF::getSession()->getVar('__3rdPartyProvider')) { + $provider = WCF::getSession()->getVar('__3rdPartyProvider'); + switch ($provider) { case 'github': - // GitHub - if (WCF::getSession()->getVar('__githubData')) { - $githubData = WCF::getSession()->getVar('__githubData'); - - $this->additionalFields['authData'] = 'github:'.$githubData['id']; - - WCF::getSession()->unregister('__githubData'); - WCF::getSession()->unregister('__githubToken'); - - if (WCF::getSession()->getVar('__email') && WCF::getSession()->getVar('__email') == $this->email) { - $registerVia3rdParty = true; - } + case 'facebook': + case 'google': + if (($oauthUser = WCF::getSession()->getVar('__oauthUser'))) { + $this->additionalFields['authData'] = $provider.':'.$oauthUser->getId(); } break; case 'twitter': // Twitter if (WCF::getSession()->getVar('__twitterData')) { $twitterData = WCF::getSession()->getVar('__twitterData'); - $this->additionalFields['authData'] = 'twitter:'.(isset($twitterData['id']) ? $twitterData['id'] : $twitterData['user_id']); + $this->additionalFields['authData'] = 'twitter:'.($twitterData['id'] ?? $twitterData['user_id']); WCF::getSession()->unregister('__twitterData'); @@ -360,38 +351,16 @@ class RegisterForm extends UserAddForm { } } break; - case 'facebook': - // Facebook - if (WCF::getSession()->getVar('__facebookData')) { - $facebookData = WCF::getSession()->getVar('__facebookData'); - $this->additionalFields['authData'] = 'facebook:'.$facebookData['id']; - - WCF::getSession()->unregister('__facebookData'); - - if (isset($facebookData['email']) && $facebookData['email'] == $this->email) { - $registerVia3rdParty = true; - } - } - break; - case 'google': - // Google Plus - if (WCF::getSession()->getVar('__googleData')) { - $googleData = WCF::getSession()->getVar('__googleData'); - $this->additionalFields['authData'] = 'google:'.$googleData['sub']; - - WCF::getSession()->unregister('__googleData'); - - if (isset($googleData['email']) && $googleData['email'] == $this->email) { - $registerVia3rdParty = true; - } - } - break; } // Accounts connected to a 3rdParty login do not have passwords. $this->password = null; + + if (WCF::getSession()->getVar('__email') && WCF::getSession()->getVar('__email') == $this->email) { + $registerVia3rdParty = true; + } } - + $eventParameters = [ 'saveOptions' => $saveOptions, 'registerVia3rdParty' => $registerVia3rdParty, diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index a220ab02bc..b80ceac351 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -5066,18 +5066,18 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email} + + -  GitHub. Der Benutzername und {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} E-Mail-Adresse wurden daher bereits ausgefüllt.]]> - session->getVar('__githubUsername')}(„{$__wcf->session->getVar('__githubUsername')}“){/if} verknüpfen]]> + session->getVar('__3rdPartyProvider') === 'github'}(„{$__wcf->session->getVar('__oauthUser')->getUsername()}“){/if} verknüpfen]]> - session->getVar('__twitterUsername')}(„{$__wcf->session->getVar('__twitterUsername')}“){/if} verknüpfen]]> @@ -5086,18 +5086,16 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email} -  Facebook. Der Benutzername und {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} E-Mail-Adresse wurden daher bereits ausgefüllt.]]> - session->getVar('__facebookUsername')}(„{$__wcf->session->getVar('__facebookUsername')}“){/if} verknüpfen]]> + session->getVar('__3rdPartyProvider') === 'facebook'}(„{$$__wcf->session->getVar('__oauthUser')->getUsername()}“){/if} verknüpfen]]> -  Google. Der Benutzername und {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} E-Mail-Adresse wurden daher bereits ausgefüllt.]]> - session->getVar('__googleUsername')}(„{$__wcf->session->getVar('__googleUsername')}“){/if} verknüpfen]]> + session->getVar('__3rdPartyProvider') === 'google'}(„{$__wcf->session->getVar('__oauthUser')->getUsername()}“){/if} verknüpfen]]> diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index ac810dfb59..4d6b7768d8 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -5063,18 +5063,18 @@ You also received a list of emergency codes to use when your second factor becom + + -  GitHub. The username and your email address have therefore already been entered.]]> - session->getVar('__githubUsername')} “{$__wcf->session->getVar('__githubUsername')}”{/if}.]]> + session->getVar('__3rdPartyProvider') === 'github'} “{$__wcf->session->getVar('__oauthUser')->getUsername()}”{/if}.]]> -  Twitter. The username has therefore already been entered. Now enter your email address and you can start right away!]]> session->getVar('__twitterUsername')} “{$__wcf->session->getVar('__twitterUsername')}”{/if}.]]> @@ -5083,18 +5083,16 @@ You also received a list of emergency codes to use when your second factor becom -  Facebook. The username and your email address have therefore already been entered.]]> - session->getVar('__facebookUsername')} “{$__wcf->session->getVar('__facebookUsername')}”{/if}.]]> + session->getVar('__3rdPartyProvider') === 'facebook'} “{$__wcf->session->getVar('__oauthUser')->getUsername()}”{/if}.]]> -  Google. The username and your email address have therefore already been entered.]]> - session->getVar('__googleUsername')} “{$__wcf->session->getVar('__googleUsername')}”{/if}.]]> + session->getVar('__3rdPartyProvider') === 'google'} “{$__wcf->session->getVar('__oauthUser')->getUsername()}”{/if}.]]> -- 2.20.1