Merge branch '5.5'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / action / GoogleAuthAction.class.php
1 <?php
2
3 namespace wcf\action;
4
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;
16 use wcf\system\WCF;
17 use wcf\util\JSON;
18 use wcf\util\StringUtil;
19
20 /**
21 * Performs authentication against Google (GAIA).
22 *
23 * @author Tim Duesterhus
24 * @copyright 2001-2021 WoltLab GmbH
25 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
26 */
27 final class GoogleAuthAction extends AbstractOauth2Action
28 {
29 /**
30 * @inheritDoc
31 */
32 public $neededModules = ['GOOGLE_PUBLIC_KEY', 'GOOGLE_PRIVATE_KEY'];
33
34 /**
35 * @var array
36 */
37 private $configuration;
38
39 /**
40 * Returns Google's OpenID Connect configuration.
41 */
42 private function getConfiguration()
43 {
44 if (!isset($this->configuration)) {
45 $request = new Request('GET', 'https://accounts.google.com/.well-known/openid-configuration');
46 $response = $this->getHttpClient()->send($request);
47
48 $this->configuration = JSON::decode((string)$response->getBody());
49 }
50
51 return $this->configuration;
52 }
53
54 /**
55 * @inheritDoc
56 */
57 protected function getTokenEndpoint(): string
58 {
59 return $this->getConfiguration()['token_endpoint'];
60 }
61
62 /**
63 * @inheritDoc
64 */
65 protected function getClientId(): string
66 {
67 return StringUtil::trim(GOOGLE_PUBLIC_KEY);
68 }
69
70 /**
71 * @inheritDoc
72 */
73 protected function getClientSecret(): string
74 {
75 return StringUtil::trim(GOOGLE_PRIVATE_KEY);
76 }
77
78 /**
79 * @inheritDoc
80 */
81 protected function getScope(): string
82 {
83 return 'profile openid email';
84 }
85
86 /**
87 * @inheritDoc
88 */
89 protected function getAuthorizeUrl(): string
90 {
91 return $this->getConfiguration()['authorization_endpoint'];
92 }
93
94 /**
95 * @inheritDoc
96 */
97 protected function getCallbackUrl(): string
98 {
99 return LinkHandler::getInstance()->getControllerLink(self::class);
100 }
101
102 /**
103 * @inheritDoc
104 */
105 protected function supportsState(): bool
106 {
107 return true;
108 }
109
110 /**
111 * @inheritDoc
112 */
113 protected function getUser(array $accessToken): OauthUser
114 {
115 $request = new Request('GET', $this->getConfiguration()['userinfo_endpoint'], [
116 'accept' => 'application/json',
117 'authorization' => \sprintf('Bearer %s', $accessToken['access_token']),
118 ]);
119 $response = $this->getHttpClient()->send($request);
120 $parsed = JSON::decode((string)$response->getBody());
121
122 $parsed['__id'] = $parsed['sub'];
123 $parsed['__username'] = $parsed['name'];
124 if ($parsed['email']) {
125 $parsed['__email'] = $parsed['email'];
126 }
127 $parsed['accessToken'] = $accessToken;
128
129 return new OauthUser($parsed);
130 }
131
132 /**
133 * @inheritDoc
134 */
135 protected function processUser(OauthUser $oauthUser): ResponseInterface
136 {
137 $user = User::getUserByAuthData('google:' . $oauthUser->getId());
138
139 if ($user->userID) {
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.
143
144 throw new NamedUserException(
145 WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.google.connect.error.inuse')
146 );
147 } else {
148 // This account belongs to an existing user, we are not logged in.
149 // Perform the login.
150
151 WCF::getSession()->changeUser($user);
152 WCF::getSession()->update();
153 EventHandler::getInstance()->fire(
154 new UserLoggedIn($user)
155 );
156
157 return new RedirectResponse(
158 LinkHandler::getInstance()->getLink()
159 );
160 }
161 } else {
162 WCF::getSession()->register('__3rdPartyProvider', 'google');
163
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.
167
168 WCF::getSession()->register('__oauthUser', $oauthUser);
169
170 return new RedirectResponse(
171 LinkHandler::getInstance()->getControllerLink(
172 AccountManagementForm::class,
173 [],
174 '#3rdParty'
175 )
176 );
177 } else {
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.
180
181 WCF::getSession()->register('__oauthUser', $oauthUser);
182 WCF::getSession()->register('__username', $oauthUser->getUsername());
183 WCF::getSession()->register('__email', $oauthUser->getEmail());
184
185 // We assume that bots won't register an external account first, so
186 // we skip the captcha.
187 WCF::getSession()->register('noRegistrationCaptcha', true);
188
189 WCF::getSession()->update();
190
191 return new RedirectResponse(
192 LinkHandler::getInstance()->getControllerLink(RegisterForm::class)
193 );
194 }
195 }
196 }
197 }