Commit | Line | Data |
---|---|---|
320f4a6d MW |
1 | <?php |
2 | namespace wcf\action; | |
3 | use wcf\data\user\User; | |
4 | use wcf\data\user\UserEditor; | |
5 | use wcf\system\exception\IllegalLinkException; | |
6 | use wcf\system\exception\NamedUserException; | |
7 | use wcf\system\exception\SystemException; | |
8 | use wcf\system\request\LinkHandler; | |
9 | use wcf\system\user\authentication\UserAuthenticationFactory; | |
10 | use wcf\system\WCF; | |
11 | use wcf\util\HeaderUtil; | |
12 | use wcf\util\HTTPRequest; | |
13 | use 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 | */ |
23 | class 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 | } |