Add AbstractOauth2Action
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / action / GithubAuthAction.class.php
CommitLineData
320f4a6d
MW
1<?php
2namespace wcf\action;
c5fc8e59 3use ParagonIE\ConstantTime\Hex;
320f4a6d
MW
4use wcf\data\user\User;
5use wcf\data\user\UserEditor;
6use wcf\system\exception\IllegalLinkException;
7use wcf\system\exception\NamedUserException;
8use wcf\system\exception\SystemException;
9use wcf\system\request\LinkHandler;
10use wcf\system\user\authentication\UserAuthenticationFactory;
11use wcf\system\WCF;
12use wcf\util\HeaderUtil;
13use wcf\util\HTTPRequest;
14use wcf\util\JSON;
15use 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 */
25class 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}