5 use GuzzleHttp\Psr7\Request
;
6 use Laminas\Diactoros\Response\RedirectResponse
;
7 use Psr\Http\Message\ResponseInterface
;
8 use wcf\data\user\User
;
9 use wcf\form\AccountManagementForm
;
10 use wcf\form\RegisterForm
;
11 use wcf\system\event\EventHandler
;
12 use wcf\system\exception\NamedUserException
;
13 use wcf\system\request\LinkHandler
;
14 use wcf\system\user\authentication\event\UserLoggedIn
;
15 use wcf\system\user\authentication\oauth\User
as OauthUser
;
18 use wcf\util\StringUtil
;
21 * Performs authentication against Google (GAIA).
23 * @author Tim Duesterhus
24 * @copyright 2001-2021 WoltLab GmbH
25 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
27 final class GoogleAuthAction
extends AbstractOauth2Action
32 public $neededModules = ['GOOGLE_PUBLIC_KEY', 'GOOGLE_PRIVATE_KEY'];
37 private $configuration;
40 * Returns Google's OpenID Connect configuration.
42 private function getConfiguration()
44 if (!isset($this->configuration
)) {
45 $request = new Request('GET', 'https://accounts.google.com/.well-known/openid-configuration');
46 $response = $this->getHttpClient()->send($request);
48 $this->configuration
= JSON
::decode((string)$response->getBody());
51 return $this->configuration
;
57 protected function getTokenEndpoint(): string
59 return $this->getConfiguration()['token_endpoint'];
65 protected function getClientId(): string
67 return StringUtil
::trim(GOOGLE_PUBLIC_KEY
);
73 protected function getClientSecret(): string
75 return StringUtil
::trim(GOOGLE_PRIVATE_KEY
);
81 protected function getScope(): string
83 return 'profile openid email';
89 protected function getAuthorizeUrl(): string
91 return $this->getConfiguration()['authorization_endpoint'];
97 protected function getCallbackUrl(): string
99 return LinkHandler
::getInstance()->getControllerLink(self
::class);
105 protected function supportsState(): bool
113 protected function getUser(array $accessToken): OauthUser
115 $request = new Request('GET', $this->getConfiguration()['userinfo_endpoint'], [
116 'accept' => 'application/json',
117 'authorization' => \
sprintf('Bearer %s', $accessToken['access_token']),
119 $response = $this->getHttpClient()->send($request);
120 $parsed = JSON
::decode((string)$response->getBody());
122 $parsed['__id'] = $parsed['sub'];
123 $parsed['__username'] = $parsed['name'];
124 if ($parsed['email']) {
125 $parsed['__email'] = $parsed['email'];
127 $parsed['accessToken'] = $accessToken;
129 return new OauthUser($parsed);
135 protected function processUser(OauthUser
$oauthUser): ResponseInterface
137 $user = User
::getUserByAuthData('google:' . $oauthUser->getId());
140 if (WCF
::getUser()->userID
) {
141 // This account belongs to an existing user, but we are already logged in.
142 // This can't be handled.
144 throw new NamedUserException(
145 WCF
::getLanguage()->getDynamicVariable('wcf.user.3rdparty.google.connect.error.inuse')
148 // This account belongs to an existing user, we are not logged in.
149 // Perform the login.
151 WCF
::getSession()->changeUser($user);
152 WCF
::getSession()->update();
153 EventHandler
::getInstance()->fire(
154 new UserLoggedIn($user)
157 return new RedirectResponse(
158 LinkHandler
::getInstance()->getLink()
162 WCF
::getSession()->register('__3rdPartyProvider', 'google');
164 if (WCF
::getUser()->userID
) {
165 // This account does not belong to anyone and we are already logged in.
166 // Thus we want to connect this account.
168 WCF
::getSession()->register('__oauthUser', $oauthUser);
170 return new RedirectResponse(
171 LinkHandler
::getInstance()->getControllerLink(
172 AccountManagementForm
::class,
178 // This account does not belong to anyone and we are not logged in.
179 // Thus we want to connect this account to a newly registered user.
181 WCF
::getSession()->register('__oauthUser', $oauthUser);
182 WCF
::getSession()->register('__username', $oauthUser->getUsername());
183 WCF
::getSession()->register('__email', $oauthUser->getEmail());
185 // We assume that bots won't register an external account first, so
186 // we skip the captcha.
187 WCF
::getSession()->register('noRegistrationCaptcha', true);
189 WCF
::getSession()->update();
191 return new RedirectResponse(
192 LinkHandler
::getInstance()->getControllerLink(RegisterForm
::class)