Merge branch '3.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / action / TwitterAuthAction.class.php
CommitLineData
320f4a6d
MW
1<?php
2namespace wcf\action;
3use wcf\data\user\User;
4use wcf\data\user\UserEditor;
5use wcf\system\exception\IllegalLinkException;
6use wcf\system\exception\NamedUserException;
7use wcf\system\exception\SystemException;
8use wcf\system\request\LinkHandler;
9use wcf\system\user\authentication\UserAuthenticationFactory;
10use wcf\system\WCF;
11use wcf\util\HeaderUtil;
12use wcf\util\HTTPRequest;
13use wcf\util\StringUtil;
14
15/**
16 * Handles twitter auth.
17 *
18 * @author Tim Duesterhus
c839bd49 19 * @copyright 2001-2018 WoltLab GmbH
320f4a6d 20 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 21 * @package WoltLabSuite\Core\Action
320f4a6d
MW
22 */
23class TwitterAuthAction extends AbstractAction {
24 /**
0fcfe5f6 25 * @inheritDoc
320f4a6d 26 */
058cbd6a 27 public $neededModules = ['TWITTER_PUBLIC_KEY', 'TWITTER_PRIVATE_KEY'];
320f4a6d
MW
28
29 /**
0fcfe5f6 30 * @inheritDoc
320f4a6d
MW
31 */
32 public function execute() {
33 parent::execute();
34
35 // user accepted
36 if (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) {
37 // fetch data created in the first step
38 $initData = WCF::getSession()->getVar('__twitterInit');
f5f2f408 39 WCF::getSession()->unregister('__twitterInit');
320f4a6d
MW
40 if (!$initData) throw new IllegalLinkException();
41
42 // validate oauth_token
43 if ($_GET['oauth_token'] !== $initData['oauth_token']) throw new IllegalLinkException();
44
45 try {
46 // fetch access_token
058cbd6a 47 $oauthHeader = [
8fa67fb3 48 'oauth_consumer_key' => StringUtil::trim(TWITTER_PUBLIC_KEY),
320f4a6d
MW
49 'oauth_nonce' => StringUtil::getRandomID(),
50 'oauth_signature_method' => 'HMAC-SHA1',
51 'oauth_timestamp' => TIME_NOW,
52 'oauth_version' => '1.0',
53 'oauth_token' => $initData['oauth_token']
058cbd6a
MS
54 ];
55 $postData = [
320f4a6d 56 'oauth_verifier' => $_GET['oauth_verifier']
058cbd6a 57 ];
320f4a6d
MW
58
59 $signature = $this->createSignature('https://api.twitter.com/oauth/access_token', array_merge($oauthHeader, $postData));
60 $oauthHeader['oauth_signature'] = $signature;
61
058cbd6a 62 $request = new HTTPRequest('https://api.twitter.com/oauth/access_token', [], $postData);
320f4a6d
MW
63 $request->addHeader('Authorization', 'OAuth '.$this->buildOAuthHeader($oauthHeader));
64 $request->execute();
65 $reply = $request->getReply();
66 $content = $reply['body'];
67 }
68 catch (SystemException $e) {
ca3579ce 69 \wcf\functions\exception\logThrowable($e);
320f4a6d
MW
70 throw new IllegalLinkException();
71 }
72
73 parse_str($content, $data);
74
75 // check whether a user is connected to this twitter account
6901535c 76 $user = User::getUserByAuthData('twitter:'.$data['user_id']);
320f4a6d
MW
77
78 if ($user->userID) {
79 // a user is already connected, but we are logged in, break
80 if (WCF::getUser()->userID) {
cecddd36 81 throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.twitter.connect.error.inuse'));
320f4a6d
MW
82 }
83 // perform login
84 else {
85 if (UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins()) {
86 $password = StringUtil::getRandomID();
87 $userEditor = new UserEditor($user);
058cbd6a 88 $userEditor->update(['password' => $password]);
320f4a6d
MW
89
90 // reload user to retrieve salt
91 $user = new User($user->userID);
92
93 UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $user->username, $password);
94 }
95
96 WCF::getSession()->changeUser($user);
97 WCF::getSession()->update();
98 HeaderUtil::redirect(LinkHandler::getInstance()->getLink());
99 }
100 }
101 else {
f5f2f408 102 WCF::getSession()->register('__3rdPartyProvider', 'twitter');
320f4a6d
MW
103 // save data for connection
104 if (WCF::getUser()->userID) {
105 WCF::getSession()->register('__twitterUsername', $data['screen_name']);
106 WCF::getSession()->register('__twitterData', $data);
107
108 HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty');
109 }
110 // save data and redirect to registration
111 else {
112 // fetch user data
113 $twitterData = null;
114 try {
40d66d27
TD
115 $oauthHeader = [
116 'oauth_consumer_key' => StringUtil::trim(TWITTER_PUBLIC_KEY),
117 'oauth_nonce' => StringUtil::getRandomID(),
118 'oauth_signature_method' => 'HMAC-SHA1',
119 'oauth_timestamp' => TIME_NOW,
120 'oauth_version' => '1.0',
121 'oauth_token' => $data['oauth_token']
122 ];
123 $getData = [
124 'include_email' => 'true',
125 'skip_status' => 'true'
126 ];
127 $signature = $this->createSignature('https://api.twitter.com/1.1/account/verify_credentials.json', array_merge($oauthHeader, $getData), $data['oauth_token_secret'], 'GET');
128 $oauthHeader['oauth_signature'] = $signature;
129
130 $request = new HTTPRequest('https://api.twitter.com/1.1/account/verify_credentials.json?skip_status=true&include_email=true');
131 $request->addHeader('Authorization', 'OAuth '.$this->buildOAuthHeader($oauthHeader));
320f4a6d
MW
132 $request->execute();
133 $reply = $request->getReply();
134 $twitterData = json_decode($reply['body'], true);
135 }
136 catch (SystemException $e) { /* ignore errors */ }
137
138 WCF::getSession()->register('__username', $data['screen_name']);
40d66d27 139 if (isset($twitterData['email'])) WCF::getSession()->register('__email', $twitterData['email']);
320f4a6d
MW
140
141 if ($twitterData !== null) $data = $twitterData;
142 WCF::getSession()->register('__twitterData', $data);
143
144 // we assume that bots won't register on twitter 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
159 // user declined
160 if (isset($_GET['denied'])) {
6f49de3a 161 throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.3rdparty.twitter.login.error.denied'));
320f4a6d
MW
162 }
163
164 // start auth by fetching request_token
165 try {
b37952e9 166 $callbackURL = LinkHandler::getInstance()->getLink('TwitterAuth');
058cbd6a 167 $oauthHeader = [
320f4a6d 168 'oauth_callback' => $callbackURL,
8fa67fb3 169 'oauth_consumer_key' => StringUtil::trim(TWITTER_PUBLIC_KEY),
320f4a6d
MW
170 'oauth_nonce' => StringUtil::getRandomID(),
171 'oauth_signature_method' => 'HMAC-SHA1',
172 'oauth_timestamp' => TIME_NOW,
173 'oauth_version' => '1.0'
058cbd6a 174 ];
320f4a6d
MW
175 $signature = $this->createSignature('https://api.twitter.com/oauth/request_token', $oauthHeader);
176 $oauthHeader['oauth_signature'] = $signature;
177
178 // call api
058cbd6a 179 $request = new HTTPRequest('https://api.twitter.com/oauth/request_token', ['method' => 'POST']);
320f4a6d
MW
180 $request->addHeader('Authorization', 'OAuth '.$this->buildOAuthHeader($oauthHeader));
181 $request->execute();
182 $reply = $request->getReply();
183
184 $content = $reply['body'];
185 }
186 catch (SystemException $e) {
ca3579ce 187 \wcf\functions\exception\logThrowable($e);
320f4a6d
MW
188 throw new IllegalLinkException();
189 }
190
191 parse_str($content, $data);
192 if ($data['oauth_callback_confirmed'] != 'true') throw new IllegalLinkException();
193
194 WCF::getSession()->register('__twitterInit', $data);
195 // redirect to twitter
196 HeaderUtil::redirect('https://api.twitter.com/oauth/authenticate?oauth_token='.rawurlencode($data['oauth_token']));
197
198 $this->executed();
199 exit;
200 }
201
202 /**
203 * Builds the OAuth authorization header.
204 *
1615fc2e 205 * @param array $parameters
320f4a6d
MW
206 * @return string
207 */
208 public function buildOAuthHeader(array $parameters) {
209 $header = '';
210 foreach ($parameters as $key => $val) {
211 if ($header !== '') $header .= ', ';
212 $header .= rawurlencode($key).'="'.rawurlencode($val).'"';
213 }
214
215 return $header;
216 }
217
218 /**
219 * Creates an OAuth 1 signature.
220 *
1615fc2e
F
221 * @param string $url
222 * @param array $parameters
223 * @param string $tokenSecret
224 * @param string $method
320f4a6d
MW
225 * @return string
226 */
40d66d27 227 public function createSignature($url, array $parameters, $tokenSecret = '', $method = 'POST') {
058cbd6a 228 $tmp = [];
320f4a6d
MW
229 foreach ($parameters as $key => $val) {
230 $tmp[rawurlencode($key)] = rawurlencode($val);
231 }
232 $parameters = $tmp;
233
234 uksort($parameters, 'strcmp');
235 $parameterString = '';
236 foreach ($parameters as $key => $val) {
237 if ($parameterString !== '') $parameterString .= '&';
238 $parameterString .= $key.'='.$val;
239 }
240
40d66d27 241 $base = $method."&".rawurlencode($url)."&".rawurlencode($parameterString);
8fa67fb3 242 $key = rawurlencode(StringUtil::trim(TWITTER_PRIVATE_KEY)).'&'.rawurlencode($tokenSecret);
320f4a6d
MW
243
244 return base64_encode(hash_hmac('sha1', $base, $key, true));
245 }
b03ba9a3 246}