Commit | Line | Data |
---|---|---|
320f4a6d | 1 | <?php |
a9229942 | 2 | |
320f4a6d | 3 | namespace wcf\action; |
a9229942 | 4 | |
8b2a995f | 5 | use GuzzleHttp\Psr7\Request; |
48e906fa | 6 | use Laminas\Diactoros\Response\RedirectResponse; |
85176ea5 | 7 | use Psr\Http\Client\ClientExceptionInterface; |
320f4a6d | 8 | use wcf\data\user\User; |
1da19348 | 9 | use wcf\form\AccountManagementForm; |
8b2a995f | 10 | use wcf\form\RegisterForm; |
301d5ea4 | 11 | use wcf\system\event\EventHandler; |
320f4a6d | 12 | use wcf\system\exception\NamedUserException; |
320f4a6d | 13 | use wcf\system\request\LinkHandler; |
301d5ea4 | 14 | use wcf\system\user\authentication\event\UserLoggedIn; |
8b2a995f | 15 | use wcf\system\user\authentication\oauth\User as OauthUser; |
320f4a6d | 16 | use wcf\system\WCF; |
320f4a6d MW |
17 | use wcf\util\JSON; |
18 | use wcf\util\StringUtil; | |
19 | ||
20 | /** | |
8b2a995f | 21 | * Performs authentication against GitHub.com |
a9229942 TD |
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 | * @package WoltLabSuite\Core\Action | |
320f4a6d | 27 | */ |
a9229942 TD |
28 | final class GithubAuthAction extends AbstractOauth2Action |
29 | { | |
30 | /** | |
31 | * @inheritDoc | |
32 | */ | |
33 | public $neededModules = ['GITHUB_PUBLIC_KEY', 'GITHUB_PRIVATE_KEY']; | |
34 | ||
35 | /** | |
36 | * @inheritDoc | |
37 | */ | |
38 | protected function getTokenEndpoint(): string | |
39 | { | |
40 | return 'https://github.com/login/oauth/access_token'; | |
41 | } | |
42 | ||
43 | /** | |
44 | * @inheritDoc | |
45 | */ | |
46 | protected function getClientId(): string | |
47 | { | |
48 | return StringUtil::trim(GITHUB_PUBLIC_KEY); | |
49 | } | |
50 | ||
51 | /** | |
52 | * @inheritDoc | |
53 | */ | |
54 | protected function getClientSecret(): string | |
55 | { | |
56 | return StringUtil::trim(GITHUB_PRIVATE_KEY); | |
57 | } | |
58 | ||
59 | /** | |
60 | * @inheritDoc | |
61 | */ | |
62 | protected function getScope(): string | |
63 | { | |
64 | return 'user:email'; | |
65 | } | |
66 | ||
67 | /** | |
68 | * @inheritDoc | |
69 | */ | |
70 | protected function getAuthorizeUrl(): string | |
71 | { | |
72 | return 'https://github.com/login/oauth/authorize'; | |
73 | } | |
74 | ||
75 | /** | |
76 | * @inheritDoc | |
77 | */ | |
78 | protected function getCallbackUrl(): string | |
79 | { | |
80 | return LinkHandler::getInstance()->getControllerLink(self::class); | |
81 | } | |
82 | ||
83 | /** | |
84 | * @inheritDoc | |
85 | */ | |
86 | protected function supportsState(): bool | |
87 | { | |
88 | return true; | |
89 | } | |
90 | ||
91 | /** | |
92 | * @inheritDoc | |
93 | */ | |
94 | protected function getUser(array $accessToken): OauthUser | |
95 | { | |
96 | $request = new Request('GET', 'https://api.github.com/user', [ | |
97 | 'accept' => 'application/json', | |
98 | 'authorization' => \sprintf('Bearer %s', $accessToken['access_token']), | |
99 | ]); | |
100 | $response = $this->getHttpClient()->send($request); | |
101 | $parsed = JSON::decode((string)$response->getBody()); | |
102 | ||
103 | $parsed['__id'] = $parsed['id']; | |
104 | $parsed['__username'] = $parsed['login']; | |
105 | $parsed['accessToken'] = $accessToken; | |
106 | ||
107 | return new OauthUser($parsed); | |
108 | } | |
109 | ||
110 | /** | |
111 | * @inheritDoc | |
112 | */ | |
113 | protected function processUser(OauthUser $oauthUser) | |
114 | { | |
115 | $user = User::getUserByAuthData('github:' . $oauthUser->getId()); | |
116 | ||
117 | if ($user->userID) { | |
118 | if (WCF::getUser()->userID) { | |
119 | // This account belongs to an existing user, but we are already logged in. | |
120 | // This can't be handled. | |
121 | ||
122 | throw new NamedUserException( | |
123 | WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.github.connect.error.inuse') | |
124 | ); | |
125 | } else { | |
126 | // This account belongs to an existing user, we are not logged in. | |
127 | // Perform the login. | |
128 | ||
129 | WCF::getSession()->changeUser($user); | |
130 | WCF::getSession()->update(); | |
301d5ea4 TD |
131 | EventHandler::getInstance()->fire( |
132 | new UserLoggedIn($user) | |
133 | ); | |
134 | ||
48e906fa TD |
135 | return new RedirectResponse( |
136 | LinkHandler::getInstance()->getLink() | |
137 | ); | |
a9229942 TD |
138 | } |
139 | } else { | |
140 | WCF::getSession()->register('__3rdPartyProvider', 'github'); | |
141 | ||
142 | if (WCF::getUser()->userID) { | |
143 | // This account does not belong to anyone and we are already logged in. | |
144 | // Thus we want to connect this account. | |
145 | ||
146 | WCF::getSession()->register('__oauthUser', $oauthUser); | |
147 | ||
48e906fa | 148 | return new RedirectResponse( |
1da19348 TD |
149 | LinkHandler::getInstance()->getControllerLink( |
150 | AccountManagementForm::class, | |
151 | [], | |
152 | '#3rdParty' | |
153 | ) | |
48e906fa | 154 | ); |
a9229942 TD |
155 | } else { |
156 | // This account does not belong to anyone and we are not logged in. | |
157 | // Thus we want to connect this account to a newly registered user. | |
158 | ||
159 | try { | |
160 | $request = new Request('GET', 'https://api.github.com/user/emails', [ | |
161 | 'accept' => 'application/json', | |
162 | 'authorization' => \sprintf('Bearer %s', $oauthUser["accessToken"]["access_token"]), | |
163 | ]); | |
164 | $response = $this->getHttpClient()->send($request); | |
165 | $emails = JSON::decode((string)$response->getBody()); | |
166 | ||
167 | // search primary email | |
168 | $email = $emails[0]['email']; | |
169 | foreach ($emails as $tmp) { | |
170 | if ($tmp['primary']) { | |
171 | $email = $tmp['email']; | |
172 | break; | |
173 | } | |
174 | } | |
175 | $oauthUser["__email"] = $email; | |
85176ea5 | 176 | } catch (ClientExceptionInterface $e) { |
a9229942 TD |
177 | } |
178 | ||
179 | WCF::getSession()->register('__oauthUser', $oauthUser); | |
180 | WCF::getSession()->register('__username', $oauthUser->getUsername()); | |
181 | WCF::getSession()->register('__email', $oauthUser->getEmail()); | |
182 | ||
183 | // We assume that bots won't register an external account first, so | |
184 | // we skip the captcha. | |
185 | WCF::getSession()->register('noRegistrationCaptcha', true); | |
186 | ||
187 | WCF::getSession()->update(); | |
a9229942 | 188 | |
48e906fa TD |
189 | return new RedirectResponse( |
190 | LinkHandler::getInstance()->getControllerLink(RegisterForm::class) | |
191 | ); | |
a9229942 TD |
192 | } |
193 | } | |
194 | } | |
320f4a6d | 195 | } |