Commit | Line | Data |
---|---|---|
320f4a6d MW |
1 | <?php |
2 | namespace wcf\action; | |
c5fc8e59 | 3 | use ParagonIE\ConstantTime\Hex; |
320f4a6d MW |
4 | use wcf\data\user\User; |
5 | use wcf\data\user\UserEditor; | |
6 | use wcf\system\exception\IllegalLinkException; | |
7 | use wcf\system\exception\NamedUserException; | |
8 | use wcf\system\exception\SystemException; | |
9 | use wcf\system\request\LinkHandler; | |
10 | use wcf\system\user\authentication\UserAuthenticationFactory; | |
11 | use wcf\system\WCF; | |
12 | use wcf\util\HeaderUtil; | |
13 | use wcf\util\HTTPRequest; | |
14 | use wcf\util\JSON; | |
15 | use wcf\util\StringUtil; | |
16 | ||
17 | /** | |
18 | * Handles github auth. | |
19 | * | |
20 | * @author Tim Duesterhus | |
7b7b9764 | 21 | * @copyright 2001-2019 WoltLab GmbH |
320f4a6d | 22 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 23 | * @package WoltLabSuite\Core\Action |
320f4a6d MW |
24 | */ |
25 | class GithubAuthAction extends AbstractAction { | |
26 | /** | |
0fcfe5f6 | 27 | * @inheritDoc |
320f4a6d | 28 | */ |
058cbd6a | 29 | public $neededModules = ['GITHUB_PUBLIC_KEY', 'GITHUB_PRIVATE_KEY']; |
320f4a6d | 30 | |
f453a22f AE |
31 | /** |
32 | * @inheritDoc | |
33 | */ | |
34 | public function readParameters() { | |
35 | parent::readParameters(); | |
36 | ||
37 | if (WCF::getSession()->spiderID) { | |
38 | throw new IllegalLinkException(); | |
39 | } | |
40 | } | |
41 | ||
320f4a6d | 42 | /** |
0fcfe5f6 | 43 | * @inheritDoc |
320f4a6d MW |
44 | */ |
45 | public function execute() { | |
46 | parent::execute(); | |
47 | ||
48 | // user accepted the connection | |
49 | if (isset($_GET['code'])) { | |
50 | try { | |
51 | // fetch access_token | |
058cbd6a | 52 | $request = new HTTPRequest('https://github.com/login/oauth/access_token', [], [ |
8fa67fb3 TD |
53 | 'client_id' => StringUtil::trim(GITHUB_PUBLIC_KEY), |
54 | 'client_secret' => StringUtil::trim(GITHUB_PRIVATE_KEY), | |
320f4a6d | 55 | 'code' => $_GET['code'] |
058cbd6a | 56 | ]); |
320f4a6d MW |
57 | $request->execute(); |
58 | $reply = $request->getReply(); | |
59 | ||
60 | $content = $reply['body']; | |
61 | } | |
62 | catch (SystemException $e) { | |
ca3579ce | 63 | \wcf\functions\exception\logThrowable($e); |
320f4a6d MW |
64 | throw new IllegalLinkException(); |
65 | } | |
66 | ||
67 | // validate state, validation of state is executed after fetching the access_token to invalidate 'code' | |
da8b0e89 | 68 | if (!isset($_GET['state']) || !WCF::getSession()->getVar('__githubInit') || !\hash_equals(WCF::getSession()->getVar('__githubInit'), $_GET['state'])) throw new IllegalLinkException(); |
f5f2f408 | 69 | WCF::getSession()->unregister('__githubInit'); |
320f4a6d MW |
70 | |
71 | parse_str($content, $data); | |
72 | ||
73 | // check whether the token is okay | |
74 | if (isset($data['error'])) throw new IllegalLinkException(); | |
75 | ||
e37288a1 TD |
76 | try { |
77 | // fetch userdata | |
a043252c NK |
78 | $request = new HTTPRequest('https://api.github.com/user'); |
79 | $request->addHeader('Authorization', 'token '.$data['access_token']); | |
e37288a1 TD |
80 | $request->execute(); |
81 | $reply = $request->getReply(); | |
82 | $userData = JSON::decode(StringUtil::trim($reply['body'])); | |
83 | } | |
84 | catch (SystemException $e) { | |
ca3579ce | 85 | \wcf\functions\exception\logThrowable($e); |
e37288a1 TD |
86 | throw new IllegalLinkException(); |
87 | } | |
88 | ||
320f4a6d | 89 | // check whether a user is connected to this github account |
6901535c | 90 | $user = User::getUserByAuthData('github:'.$userData['id']); |
e37288a1 | 91 | if (!$user->userID) { |
903c645f | 92 | $user = User::getUserByAuthData('github:'.$data['access_token']); |
e37288a1 | 93 | $userEditor = new UserEditor($user); |
058cbd6a | 94 | $userEditor->update(['authData' => 'github:'.$userData['id']]); |
e37288a1 | 95 | } |
320f4a6d MW |
96 | |
97 | if ($user->userID) { | |
98 | // a user is already connected, but we are logged in, break | |
99 | if (WCF::getUser()->userID) { | |
cecddd36 | 100 | throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.github.connect.error.inuse')); |
320f4a6d MW |
101 | } |
102 | // perform login | |
103 | else { | |
320f4a6d MW |
104 | WCF::getSession()->changeUser($user); |
105 | WCF::getSession()->update(); | |
106 | HeaderUtil::redirect(LinkHandler::getInstance()->getLink()); | |
107 | } | |
108 | } | |
109 | else { | |
f5f2f408 | 110 | WCF::getSession()->register('__3rdPartyProvider', 'github'); |
320f4a6d MW |
111 | // save data for connection |
112 | if (WCF::getUser()->userID) { | |
113 | WCF::getSession()->register('__githubUsername', $userData['login']); | |
114 | WCF::getSession()->register('__githubToken', $data['access_token']); | |
115 | ||
116 | HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty'); | |
117 | } | |
118 | // save data and redirect to registration | |
119 | else { | |
120 | WCF::getSession()->register('__githubData', $userData); | |
121 | WCF::getSession()->register('__username', $userData['login']); | |
122 | ||
e37288a1 | 123 | try { |
a043252c NK |
124 | $request = new HTTPRequest('https://api.github.com/user/emails'); |
125 | $request->addHeader('Authorization', 'token '.$data['access_token']); | |
e37288a1 TD |
126 | $request->execute(); |
127 | $reply = $request->getReply(); | |
128 | $emails = JSON::decode(StringUtil::trim($reply['body'])); | |
129 | ||
130 | // search primary email | |
131 | $email = $emails[0]['email']; | |
132 | foreach ($emails as $tmp) { | |
ee92fc62 NK |
133 | if ($tmp['primary']) { |
134 | $email = $tmp['email']; | |
135 | break; | |
136 | } | |
320f4a6d | 137 | } |
e37288a1 TD |
138 | WCF::getSession()->register('__email', $email); |
139 | } | |
140 | catch (SystemException $e) { | |
320f4a6d | 141 | } |
320f4a6d MW |
142 | WCF::getSession()->register('__githubToken', $data['access_token']); |
143 | ||
144 | // we assume that bots won't register on github first | |
ed7256dd | 145 | // thus no need for a captcha |
fbb526f2 | 146 | if (REGISTER_USE_CAPTCHA) { |
ed7256dd MS |
147 | WCF::getSession()->register('noRegistrationCaptcha', true); |
148 | } | |
320f4a6d MW |
149 | |
150 | WCF::getSession()->update(); | |
151 | HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register')); | |
152 | } | |
153 | } | |
154 | ||
155 | $this->executed(); | |
156 | exit; | |
157 | } | |
158 | // user declined or any other error that may occur | |
159 | if (isset($_GET['error'])) { | |
6f49de3a | 160 | throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.github.login.error.'.$_GET['error'])); |
320f4a6d MW |
161 | } |
162 | ||
163 | // start auth by redirecting to github | |
c5fc8e59 | 164 | $token = Hex::encode(\random_bytes(20)); |
320f4a6d | 165 | WCF::getSession()->register('__githubInit', $token); |
8fa67fb3 | 166 | HeaderUtil::redirect("https://github.com/login/oauth/authorize?client_id=".rawurlencode(StringUtil::trim(GITHUB_PUBLIC_KEY))."&scope=".rawurlencode('user:email')."&state=".$token); |
320f4a6d MW |
167 | $this->executed(); |
168 | exit; | |
169 | } | |
320f4a6d | 170 | } |