Client::obtainRequestToken()   D
last analyzed

Complexity

Conditions 9
Paths 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 31
rs 4.909
cc 9
eloc 17
nc 6
nop 1
1
<?php
2
/**
3
 * Client.php
4
 *
5
 * @copyright	More in license.md
6
 * @license		http://www.ipublikuj.eu
7
 * @author		Adam Kadlec http://www.ipublikuj.eu
8
 * @package		iPublikuj:Twitter!
9
 * @subpackage	common
10
 * @since		5.0
11
 *
12
 * @date		01.03.15
13
 */
14
15
namespace IPub\Twitter;
16
17
use Nette;
18
use Nette\Http;
19
use Nette\Utils;
20
21
use IPub;
22
use IPub\Twitter;
23
use IPub\Twitter\Api;
24
25
use IPub\OAuth;
26
27
/**
28
 * Twitter's API and OAuth client
29
 *
30
 * @package		iPublikuj:Twitter!
31
 * @subpackage	common
32
 *
33
 * @author Adam Kadlec <[email protected]>
34
 */
35
class Client extends ApiCall
36
{
37
	/**
38
	 * @var SessionStorage
39
	 */
40
	private $session;
41
42
	/**
43
	 * @var Http\IRequest
44
	 */
45
	private $httpRequest;
46
47
	/**
48
	 * The ID of the Twitter user, or 0 if the user is logged out
49
	 *
50
	 * @var integer
51
	 */
52
	private $user;
53
54
	/**
55
	 * The OAuth access token received in exchange for a valid authorization code
56
	 * null means the access token has yet to be determined
57
	 *
58
	 * @var OAuth\Token
59
	 */
60
	private $accessToken;
61
62
	/**
63
	 * @param OAuth\Consumer $consumer
64
	 * @param OAuth\HttpClient $httpClient
65
	 * @param Configuration $config
66
	 * @param SessionStorage $session
67
	 * @param Http\IRequest $httpRequest
68
	 */
69
	public function __construct(
70
		OAuth\Consumer $consumer,
71
		OAuth\HttpClient $httpClient,
72
		Configuration $config,
73
		SessionStorage $session,
74
		Nette\Http\IRequest $httpRequest
75
	){
76
		parent::__construct($consumer, $httpClient, $config);
77
78
		$this->session = $session;
79
		$this->httpRequest = $httpRequest;
80
81
		$this->consumer->setCallbackUrl($this->getCurrentUrl());
82
	}
83
84
	/**
85
	 * @return SessionStorage
86
	 */
87
	public function getSession()
88
	{
89
		return $this->session;
90
	}
91
92
	/**
93
	 * @return Http\UrlScript
94
	 */
95
	public function getCurrentUrl()
96
	{
97
		return clone $this->httpRequest->getUrl();
98
	}
99
100
	/**
101
	 * Sets the access token for api calls. Use this if you get
102
	 * your access token by other means and just want the SDK
103
	 * to use it.
104
	 *
105
	 * @param array|string $token an access token.
106
	 *
107
	 * @return $this
108
	 *
109
	 * @throws Exceptions\InvalidArgumentException
110
	 */
111
	public function setAccessToken($token)
112
	{
113
		if (!is_array($token)) {
114
			try {
115
				$token = Utils\Json::decode($token, Utils\Json::FORCE_ARRAY);
116
117
			} catch (Utils\JsonException $ex) {
118
				throw new Exceptions\InvalidArgumentException($ex->getMessage(), 0, $ex);
119
			}
120
		}
121
122
		if (!isset($token['access_token']) || !isset($token['access_token_secret'])) {
123
			throw new Exceptions\InvalidArgumentException("It's required that the token has 'access_token' and 'access_token_secret' field.");
124
		}
125
126
		$this->accessToken = new OAuth\Token($token['access_token'], $token['access_token_secret']);
127
128
		return $this;
129
	}
130
131
	/**
132
	 * Determines the access token that should be used for API calls.
133
	 * The first time this is called, $this->accessToken is set equal
134
	 * to either a valid user access token, or it's set to the application
135
	 * access token if a valid user access token wasn't available.  Subsequent
136
	 * calls return whatever the first call returned.
137
	 *
138
	 * @return OAuth\Token The access token
139
	 */
140
	public function getAccessToken()
141
	{
142
		if ($this->accessToken === NULL && ($accessToken = $this->getUserAccessToken())) {
143
			$this->setAccessToken($accessToken);
144
		}
145
146
		return $this->accessToken;
147
	}
148
149
	/**
150
	 * Determines and returns the user access token, first using
151
	 * the signed request if present, and then falling back on
152
	 * the authorization code if present.  The intent is to
153
	 * return a valid user access token, or false if one is determined
154
	 * to not be available.
155
	 *
156
	 * @return string A valid user access token, or false if one could not be determined.
157
	 */
158
	protected function getUserAccessToken()
159
	{
160
		if (($verifier = $this->getVerifier()) && ($token = $this->getToken())) {
161
			if ($this->obtainAccessToken($verifier, $token)) {
162
				return [
163
					'access_token'          => $this->session->access_token,
164
					'access_token_secret'   => $this->session->access_token_secret
165
				];
166
			}
167
168
			// verifier was bogus, so everything based on it should be invalidated.
169
			$this->session->clearAll();
170
171
			return FALSE;
172
		}
173
174
		// as a fallback, just return whatever is in the persistent
175
		// store, knowing nothing explicit (signed request, authorization
176
		// code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
177
		// but it's the same as what's in the persistent store)
178
		return [
179
			'access_token'          => $this->session->access_token,
180
			'access_token_secret'   => $this->session->access_token_secret
181
		];
182
	}
183
184
	/**
185
	 * Get the UID of the connected user, or 0 if the Twitter user is not connected.
186
	 *
187
	 * @return string the UID if available.
188
	 */
189
	public function getUser()
190
	{
191
		if ($this->user === NULL) {
192
			$this->user = $this->getUserFromAvailableData();
193
		}
194
195
		return $this->user;
196
	}
197
198
	/**
199
	 * @param int|string $profileId
200
	 *
201
	 * @return Profile
202
	 */
203
	public function getProfile($profileId = NULL)
204
	{
205
		return new Profile($this, $profileId);
206
	}
207
208
	/**
209
	 * Retrieves the UID with the understanding that $this->accessToken has already been set and is seemingly legitimate
210
	 * It relies on Twitter's API to retrieve user information and then extract the user ID.
211
	 *
212
	 * @return integer Returns the UID of the Twitter user, or 0 if the Twitter user could not be determined
213
	 */
214
	protected function getUserFromAccessToken()
215
	{
216
		try {
217
			$user = $this->get('account/verify_credentials.json');
218
219
			if ($user instanceof Utils\ArrayHash) {
220
				return $user->id;
221
			}
222
223
		// User could not be checked through API calls
224
		} catch (\Exception $e) {
225
			// Is not necessary to throw exception
226
			// when call fails. This fail was already logged.
227
		}
228
229
		return 0;
230
	}
231
232
	/**
233
	 * Determines the connected user by first examining any signed
234
	 * requests, then considering an authorization code, and then
235
	 * falling back to any persistent store storing the user.
236
	 *
237
	 * @return integer The id of the connected Twitter user, or 0 if no such user exists
238
	 */
239
	protected function getUserFromAvailableData()
240
	{
241
		$user = $this->session->get('user_id', 0);
242
243
		// use access_token to fetch user id if we have a user access_token, or if
244
		// the cached access token has changed
245
		if (($accessToken = $this->getAccessToken()) && !($user && $this->session->access_token === $accessToken->getToken())) {
246
			if (!$user = $this->getUserFromAccessToken()) {
247
				$this->session->clearAll();
248
249
			} else {
250
				$this->session->user_id = $user;
251
			}
252
		}
253
254
		return $user;
255
	}
256
257
	/**
258
	 * Get a request token from Twitter
259
	 *
260
	 * @param string|null $callback
261
	 *
262
	 * @return bool
263
	 */
264
	public function obtainRequestToken($callback = NULL)
265
	{
266
		// Before first handshake, session have to cleared
267
		$this->session->clearAll();
268
269
		// Complete request params
270
		$params = [
271
			'oauth_callback' => $callback ?:$this->consumer->getCallbackUrl(),
272
		];
273
274
		$response = $this->httpClient->makeRequest(
275
			new Api\Request($this->consumer, $this->config->createUrl('oauth', 'request_token', $params), OAuth\Api\Request::POST),
276
			'HMAC-SHA1'
277
		);
278
279
		if (!$response->isOk() || !$response->isQueryString() || (!$data = Utils\ArrayHash::from($response->toArray()))) {
280
			return FALSE;
281
282
		} else if ($data->offsetExists('oauth_callback_confirmed')
283
			&& Utils\Strings::lower($data->oauth_callback_confirmed) == 'true'
284
			&& $data->offsetExists('oauth_token')
285
			&& $data->offsetExists('oauth_token_secret')
286
		) {
287
			$this->session->request_token = $data->oauth_token;
288
			$this->session->request_token_secret = $data->oauth_token_secret;
289
290
			return TRUE;
291
		}
292
293
		return FALSE;
294
	}
295
296
	/**
297
	 * Retrieves an access token and access token secret for the given authorization verifier and token
298
	 * (previously generated from www.twitter.com on behalf of a specific user).
299
	 * The authorization verifier and token is sent to www.twitter.com/services/oauth
300
	 * and a legitimate access token is generated provided the access token
301
	 * and the user for which it was generated all match, and the user is
302
	 * either logged in to Twitter or has granted an offline access permission
303
	 *
304
	 * @param string $verifier
305
	 * @param string $token
306
	 *
307
	 * @return bool
308
	 */
309
	protected function obtainAccessToken($verifier, $token)
310
	{
311
		if (empty($verifier) || empty($token)) {
312
			return FALSE;
313
		}
314
315
		// Complete request params
316
		$params = [
317
			'oauth_token' =>  $token,
318
			'oauth_verifier' => $verifier,
319
		];
320
321
		$token = new OAuth\Token($this->session->request_token, $this->session->request_token_secret);
322
323
		$response = $this->httpClient->makeRequest(
324
			new Api\Request($this->consumer, $this->config->createUrl('oauth', 'access_token', $params), OAuth\Api\Request::POST, [], [], $token),
325
			'HMAC-SHA1'
326
		);
327
328
		if (!$response->isOk() || !$response->isQueryString() || (!$data = Utils\ArrayHash::from($response->toArray()))) {
329
			// most likely that user very recently revoked authorization.
330
			// In any event, we don't have an access token, so say so.
331
			return FALSE;
332
333
		} else if ($data->offsetExists('oauth_token') && $data->offsetExists('oauth_token_secret')) {
334
			// Clear unused variables
335
			$this->session->clearAll();
336
337
			// Store access token to session
338
			$this->session->access_token = $data->oauth_token;
339
			$this->session->access_token_secret = $data->oauth_token_secret;
340
341
			return TRUE;
342
		}
343
344
		return FALSE;
345
	}
346
347
	/**
348
	 * Get the authorization verifier from the query parameters, if it exists,
349
	 * and otherwise return false to signal no authorization verifier was
350
	 * discoverable.
351
	 *
352
	 * @return mixed The authorization verifier, or false if the authorization verifier could not be determined.
353
	 */
354
	protected function getVerifier()
355
	{
356
		if ($verifier = $this->getRequest('oauth_verifier')) {
357
			return $verifier;
358
		}
359
360
		return FALSE;
361
	}
362
363
	/**
364
	 * Get the authorization verifier from the query parameters, if it exists,
365
	 * and otherwise return false to signal no authorization verifier was
366
	 * discoverable.
367
	 *
368
	 * @return mixed The authorization verifier, or false if the authorization verifier could not be determined.
369
	 */
370
	protected function getToken()
371
	{
372
		if ($token = $this->getRequest('oauth_token')) {
373
			return $token;
374
		}
375
376
		return FALSE;
377
	}
378
379
	/**
380
	 * Destroy the current session
381
	 *
382
	 * @return $this
383
	 */
384
	public function destroySession()
385
	{
386
		$this->accessToken = NULL;
387
		$this->user = NULL;
388
		$this->session->clearAll();
389
390
		return $this;
391
	}
392
393
	/**
394
	 * @param string $key
395
	 * @param mixed $default
396
	 *
397
	 * @return mixed|null
398
	 */
399
	protected function getRequest($key, $default = NULL)
400
	{
401
		if ($value = $this->httpRequest->getPost($key)) {
402
			return $value;
403
		}
404
405
		if ($value = $this->httpRequest->getQuery($key)) {
406
			return $value;
407
		}
408
409
		return $default;
410
	}
411
}
412