PluginProbe
InfiniteWP Client / trunk
InfiniteWP Client vtrunk
1.13.10 1.13.7 trunk 0.1.4 0.1.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.11.0 1.11.1 1.12.1 1.12.3 All 92 releases
iwp-client / lib / Dropbox / OAuth / Consumer / ConsumerAbstract.php

ConsumerAbstract.php in InfiniteWP Client trunk, at lib/Dropbox/OAuth/Consumer/ConsumerAbstract.php

372 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Abstract OAuth consumer
5 * @author Ben Tadiar <ben@handcraftedbyben.co.uk>
6 * @link https://github.com/benthedesigner/dropbox
7 * @package Dropbox\OAuth
8 * @subpackage Consumer
9 */
10
11 abstract class IWP_Dropbox_OAuth_Consumer_ConsumerAbstract
12 {
13 // Dropbox web endpoint
14 const WEB_URL = 'https://www.dropbox.com/';
15
16 // OAuth flow methods
17 const REQUEST_TOKEN_METHOD = 'oauth2/REQUEST_TOKEN_METHOD';
18 const AUTHORISE_METHOD = 'oauth2/authorize';
19 const ACCESS_TOKEN_METHOD = 'oauth2/token';
20 const API_URL = 'https://api.dropbox.com/1/';
21 const OAUTH_UPGRADE = 'oauth2/token_from_oauth1';
22
23 /**
24 * Signature method, either PLAINTEXT or HMAC-SHA1
25 * @var string
26 */
27 private $sigMethod = 'PLAINTEXT';
28
29 /**
30 * Output file handle
31 * @var null|resource
32 */
33 protected $outFile = null;
34
35 /**
36 * Input file handle
37 * @var null|resource
38 */
39 protected $inFile = null;
40
41 /**
42 * OAuth token
43 * @var stdclass
44 */
45 private $token = null;
46
47 /**
48 * Acquire an unauthorised request token
49 * @link http://tools.ietf.org/html/rfc5849#section-2.1
50 * @return void
51 */
52
53 protected function authenticate()
54 {
55 global $iwp_backup_core;
56
57 $access_token = $this->storage->get('access_token');
58 //Check if the new token type is set if not they need to be upgraded to OAuth2
59 if (!empty($access_token) && isset($access_token->oauth_token) && !isset($access_token->token_type)) {
60 $iwp_backup_core->log('OAuth v1 token found: upgrading to v2');
61 $this->upgradeOAuth();
62 $iwp_backup_core->log('OAuth token upgrade successful');
63 }
64
65 if (empty($access_token) || !isset($access_token->oauth_token)) {
66 try {
67 $this->getAccessToken();
68 } catch(Exception $e) {
69 $excep_class = get_class($e);
70 // 04-Sep-2015 - Dropbox started throwing a 400, which caused a Dropbox_BadRequestException which previously wasn't being caught
71 if ('Dropbox_BadRequestException' == $excep_class || 'Dropbox_Exception' == $excep_class) {
72 global $iwp_backup_core;
73 $iwp_backup_core->log($e->getMessage().' - need to reauthenticate this site with Dropbox (if this fails, then you can also try wiping your settings from the Expert Settings section)');
74 //$this->getRequestToken();
75 $this->authorise();
76 } else {
77 throw $e;
78 }
79 }
80 }
81 }
82
83 public function getRequestToken()
84 {
85 $url = WPTC_Dropbox_API::API_URL_V2 . self::REQUEST_TOKEN_METHOD;
86 $response = $this->fetch('POST', $url, '');
87
88 return $this->parseTokenString($response['body']);
89 }
90
91 /**
92 * Build the user authorisation URL
93 * @return string
94 */
95 public function getAuthoriseUrl()
96 {
97 $params = array(
98 'client_id' => WPTC_DROPBOX_CLIENT_ID,
99 'response_type' => 'code',
100 'redirect_uri' => WPTC_DROPBOX_REDIRECT_URL,
101 'state' => WPTC_DROPBOX_WP_REDIRECT_URL,
102 );
103
104
105 // Build the URL and redirect the user
106 $query = '?' . http_build_query($params, '', '&');
107 $url = self::WEB_URL . self::AUTHORISE_METHOD . $query;
108
109 return $url;
110 }
111
112 public function upgradeOAuth()
113 {
114 // N.B. This call only exists under API v1 - i.e. there is no APIv2 equivalent. Hence the APIv1 endpoint (API_URL) is used, and not the v2 (API_URL_V2)
115
116 $url = self::API_URL . self::OAUTH_UPGRADE;
117 $config = WPTC_Factory::get('config');
118 $this->token = new stdClass();
119 $this->token->oauth_token = $config->get_option('access_token');
120 $this->token->oauth_token_secret = $config->get_option('access_token_secret');
121 $response = $this->fetch('POST', $url, '');
122 return $response['body'];
123 }
124
125 /**
126 * Acquire an access token
127 * Tokens acquired at this point should be stored to
128 * prevent having to request new tokens for each API call
129 * @link http://tools.ietf.org/html/rfc5849#section-2.3
130 */
131 public function getAccessToken()
132 {
133 // Get the signed request URL
134 $response = $this->fetch('POST', WPTC_Dropbox_API::API_URL_V2, self::ACCESS_TOKEN_METHOD);
135 return $this->parseTokenString($response['body']);
136 }
137
138 /**
139 * Generate signed request URL
140 * See inline comments for description
141 * @link http://tools.ietf.org/html/rfc5849#section-3.4
142 * @param string $method HTTP request method
143 * @param string $url API endpoint to send the request to
144 * @param string $call API call to send
145 * @param array $additional Additional parameters as an associative array
146 * @return array
147 */
148 protected function getSignedRequest($method, $url, $call, array $additional = array())
149 {
150 // Get the request/access token
151 $token = $this->getToken();
152 // Prepare the standard request parameters differently for OAuth1 and OAuth2; we still need OAuth1 to make the request to the upgrade token endpoint
153 if (!empty($token)) {
154 $params = array(
155 'access_token' => $token,
156 );
157
158 /*
159 To keep this API backwards compatible with the API v1 endpoints all v2 endpoints will also send to this method a api_v2 parameter this will then return just the access token as the signed request is not needed for any calls.
160 */
161
162 if (isset($additional['api_v2']) && $additional['api_v2'] == true) {
163 unset($additional['api_v2']);
164 if (isset($additional['content_download']) && $additional['content_download'] == true) {
165 unset($additional['content_download']);
166 $headers = array(
167 'Authorization: Bearer '.$params['access_token'],
168 'Content-Type:',
169 'Dropbox-API-Arg: '.json_encode($additional),
170 );
171 $additional = '';
172 } else if (isset($additional['content_upload']) && $additional['content_upload'] == true) {
173 unset($additional['content_upload']);
174 $headers = array(
175 'Authorization: Bearer '.$params['access_token'],
176 'Content-Type: application/octet-stream',
177 'Dropbox-API-Arg: '.json_encode($additional),
178 );
179 $additional = '';
180 } else {
181 $headers = array(
182 'Authorization: Bearer '.$params['access_token'],
183 'Content-Type: application/json',
184 );
185 }
186 return array(
187 'url' => $url . $call,
188 'postfields' => $additional,
189 'headers' => $headers,
190 );
191 }
192 } else {
193
194 // Generate a random string for the request
195 $nonce = md5(microtime(true) . uniqid('', true));
196 $params = array(
197 'oauth_consumer_key' => WPTC_DROPBOX_CLIENT_ID,
198 'oauth_token' => $this->token->oauth_token,
199 'oauth_signature_method' => $this->sigMethod,
200 'oauth_version' => '1.0',
201 // Generate nonce and timestamp if signature method is HMAC-SHA1
202 'oauth_timestamp' => ($this->sigMethod == 'HMAC-SHA1') ? time() : null,
203 'oauth_nonce' => ($this->sigMethod == 'HMAC-SHA1') ? $nonce : null,
204 );
205 }
206
207 // Merge with the additional request parameters
208 $params = array_merge($params, $additional);
209 ksort($params);
210
211 // URL encode each parameter to RFC3986 for use in the base string
212 $encoded = array();
213 foreach($params as $param => $value) {
214 if ($value !== null) {
215 // If the value is a file upload (prefixed with @), replace it with
216 // the destination filename, the file path will be sent in POSTFIELDS
217 if (isset($value[0]) && $value[0] === '@') $value = $params['filename'];
218 # Prevent spurious PHP warning by only doing non-arrays
219 if (!is_array($value)) $encoded[] = $this->encode($param) . '=' . $this->encode($value);
220 } else {
221 unset($params[$param]);
222 }
223 }
224
225 // Build the first part of the string
226 $base = $method . '&' . $this->encode($url . $call) . '&';
227
228 // Re-encode the encoded parameter string and append to $base
229 $base .= $this->encode(implode('&', $encoded));
230
231 // Concatenate the secrets with an ampersand
232 $key = WPTC_DROPBOX_CLIENT_SECRET . '&' . $this->token->oauth_token_secret;
233
234 // Get the signature string based on signature method
235 $signature = $this->getSignature($base, $key);
236 $params['oauth_signature'] = $signature;
237
238 // Build the signed request URL
239 $query = '?' . http_build_query($params, '', '&');
240 return array(
241 'url' => $url . $call . $query,
242 'postfields' => $params,
243 );
244 }
245
246 /**
247 * Generate the oauth_signature for a request
248 * @param string $base Signature base string, used by HMAC-SHA1
249 * @param string $key Concatenated consumer and token secrets
250 */
251 private function getSignature($base, $key)
252 {
253 switch ($this->sigMethod) {
254 case 'PLAINTEXT':
255 $signature = $key;
256 break;
257 case 'HMAC-SHA1':
258 $signature = base64_encode(hash_hmac('sha1', $base, $key, true));
259 break;
260 }
261
262 return $signature;
263 }
264
265 /**
266 * Set the token to use for OAuth requests
267 * @param stdtclass $token A key secret pair
268 */
269 public function setToken($token)
270 {
271
272 $this->token = $token;
273
274 return $this;
275 }
276
277 public function getToken()
278 {
279 return $this->token;
280 }
281
282 public function resetToken()
283 {
284 $token = new stdClass;
285 $token->oauth_token = false;
286 $token->oauth_token_secret = false;
287
288 $this->setToken($token);
289
290 return $this;
291 }
292
293 /**
294 * Set the OAuth signature method
295 * @param string $method Either PLAINTEXT or HMAC-SHA1
296 * @return void
297 */
298 public function setSignatureMethod($method)
299 {
300 $method = strtoupper($method);
301
302 switch ($method) {
303 case 'PLAINTEXT':
304 case 'HMAC-SHA1':
305 $this->sigMethod = $method;
306 break;
307 default:
308 throw new Exception('Unsupported signature method ' . $method);
309 }
310 }
311
312 /**
313 * Set the output file
314 * @param resource Resource to stream response data to
315 * @return void
316 */
317 public function setOutFile($handle)
318 {
319 if (!is_resource($handle) || get_resource_type($handle) != 'stream') {
320 throw new Exception('Outfile must be a stream resource');
321 }
322 $this->outFile = $handle;
323 }
324
325 /**
326 * Set the input file
327 * @param resource Resource to read data from
328 * @return void
329 */
330 public function setInFile($handle)
331 {
332 $this->inFile = $handle;
333 }
334
335 /**
336 * Parse response parameters for a token into an object
337 * Dropbox returns tokens in the response parameters, and
338 * not a JSON encoded object as per other API requests
339 * @link http://oauth.net/core/1.0/#response_parameters
340 * @param string $response
341 * @return object stdClass
342 */
343 private function parseTokenString($response)
344 {
345 if (!$response)
346 throw new Exception('Response cannot be null');
347
348 $parts = explode('&', $response);
349 $token = new stdClass();
350 foreach ($parts as $part) {
351 list($k, $v) = explode('=', $part, 2);
352 $k = strtolower($k);
353 $token->$k = $v;
354 }
355
356 return $token;
357 }
358
359 /**
360 * Encode a value to RFC3986
361 * This is a convenience method to decode ~ symbols encoded
362 * by rawurldecode. This will encode all characters except
363 * the unreserved set, ALPHA, DIGIT, '-', '.', '_', '~'
364 * @link http://tools.ietf.org/html/rfc5849#section-3.6
365 * @param mixed $value
366 */
367 private function encode($value)
368 {
369 return str_replace('%7E', '~', rawurlencode($value));
370 }
371 }
372