PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260814
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260814
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
s2member / src / includes / externals / aweber / oauth_application.php

oauth_application.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions 260814, at src/includes/externals/aweber/oauth_application.php

681 lines 20.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 if (!class_exists('CurlObject')) require_once('curl_object.php');
4 if (!class_exists('CurlResponse')) require_once('curl_response.php');
5
6 /**
7 * OAuthServiceProvider
8 *
9 * Represents the service provider in the OAuth authentication model.
10 * The class that implements the service provider will contain the
11 * specific knowledge about the API we are interfacing with, and
12 * provide useful methods for interfacing with its API.
13 *
14 * For example, an OAuthServiceProvider would know the URLs necessary
15 * to perform specific actions, the type of data that the API calls
16 * would return, and would be responsible for manipulating the results
17 * into a useful manner.
18 *
19 * It should be noted that the methods enforced by the OAuthServiceProvider
20 * interface are made so that it can interact with our OAuthApplication
21 * cleanly, rather than from a general use perspective, though some
22 * methods for those purposes do exists (such as getUserData).
23 *
24 * @package
25 * @version $id$
26 */
27 interface OAuthServiceProvider {
28
29 public function getAccessTokenUrl();
30 public function getAuthorizeUrl();
31 public function getRequestTokenUrl();
32 public function getAuthTokenFromUrl();
33 public function getBaseUri();
34 public function getUserData();
35
36 }
37
38 /**
39 * OAuthApplication
40 *
41 * Base class to represent an OAuthConsumer application. This class is
42 * intended to be extended and modified for each ServiceProvider. Each
43 * OAuthServiceProvider should have a complementary OAuthApplication
44 *
45 * The OAuthApplication class should contain any details on preparing
46 * requires that is unique or specific to that specific service provider's
47 * implementation of the OAuth model.
48 *
49 * This base class is based on OAuth 1.0, designed with AWeber's implementation
50 * as a model. An OAuthApplication built to work with a different service
51 * provider (especially an OAuth2.0 Application) may alter or bypass portions
52 * of the logic in this class to meet the needs of the service provider it
53 * is designed to interface with.
54 *
55 * @package
56 * @version $id$
57 */
58 class OAuthApplication implements AWeberOAuthAdapter {
59 public $debug = false;
60
61 public $userAgent = 'AWeber OAuth Consumer Application 1.0 - https://labs.aweber.com/';
62
63 public $format = false;
64
65 public $requiresTokenSecret = true;
66
67 public $signatureMethod = 'HMAC-SHA1';
68 public $version = '1.0';
69
70 public $curl = false;
71
72 /**
73 * @var OAuthUser User currently interacting with the service provider
74 */
75 public $user = false;
76
77 // Data binding this OAuthApplication to the consumer application it is acting
78 // as a proxy for
79 public $consumerKey = false;
80 public $consumerSecret = false;
81
82 /**
83 * __construct
84 *
85 * Create a new OAuthApplication, based on an OAuthServiceProvider
86 * @access public
87 * @return void
88 */
89 public function __construct($parentApp = false) {
90 if ($parentApp) {
91 if (!is_a($parentApp, 'OAuthServiceProvider')) {
92 throw new Exception('Parent App must be a valid OAuthServiceProvider!');
93 }
94 $this->app = $parentApp;
95 }
96 $this->user = new OAuthUser();
97 $this->curl = new CurlObject();
98 }
99
100 /**
101 * request
102 *
103 * Implemented for a standard OAuth adapter interface
104 * @param mixed $method
105 * @param mixed $uri
106 * @param array $data
107 * @param array $options
108 * @access public
109 * @return void
110 */
111 public function request($method, $uri, $data = array(), $options = array()) {
112 $uri = $this->app->removeBaseUri($uri);
113 $url = $this->app->getBaseUri() . $uri;
114
115 # WARNING: non-primative items in data must be json serialized in GET and POST.
116 if ($method == 'POST' or $method == 'GET') {
117 foreach ($data as $key => $value) {
118 if (is_array($value)) {
119 $data[$key] = json_encode($value);
120 }
121 }
122 }
123
124 $response = $this->makeRequest($method, $url, $data);
125 if (!empty($options['return'])) {
126 if ($options['return'] == 'status') {
127 return $response->headers['Status-Code'];
128 }
129 if ($options['return'] == 'headers') {
130 return $response->headers;
131 }
132 if ($options['return'] == 'integer') {
133 return intval($response->body);
134 }
135 }
136
137 $data = json_decode($response->body, true);
138
139 if (empty($options['allow_empty']) && !isset($data)) {
140 throw new AWeberResponseError($uri);
141 }
142 return $data;
143 }
144
145 /**
146 * getRequestToken
147 *
148 * Gets a new request token / secret for this user.
149 * @access public
150 * @return void
151 */
152 public function getRequestToken($callbackUrl=false) {
153 $data = ($callbackUrl)? array('oauth_callback' => $callbackUrl) : array();
154 $resp = $this->makeRequest('POST', $this->app->getRequestTokenUrl(), $data);
155 $data = $this->parseResponse($resp);
156 $this->requiredFromResponse($data, array('oauth_token', 'oauth_token_secret'));
157 $this->user->requestToken = $data['oauth_token'];
158 $this->user->tokenSecret = $data['oauth_token_secret'];
159 return $data['oauth_token'];
160 }
161
162 /**
163 * getAccessToken
164 *
165 * Makes a request for access tokens. Requires that the current user has an authorized
166 * token and token secret.
167 *
168 * @access public
169 * @return void
170 */
171 public function getAccessToken() {
172 $resp = $this->makeRequest('POST', $this->app->getAccessTokenUrl(),
173 array('oauth_verifier' => $this->user->verifier)
174 );
175 $data = $this->parseResponse($resp);
176 $this->requiredFromResponse($data, array('oauth_token', 'oauth_token_secret'));
177
178 if (empty($data['oauth_token'])) {
179 throw new AWeberOAuthDataMissing('oauth_token');
180 }
181
182 $this->user->accessToken = $data['oauth_token'];
183 $this->user->tokenSecret = $data['oauth_token_secret'];
184 return array($data['oauth_token'], $data['oauth_token_secret']);
185 }
186
187 /**
188 * parseAsError
189 *
190 * Checks if response is an error. If it is, raise an appropriately
191 * configured exception.
192 *
193 * @param mixed $response Data returned from the server, in array form
194 * @access public
195 * @throws AWeberOAuthException
196 * @return void
197 */
198 public function parseAsError($response) {
199 if (!empty($response['error'])) {
200 throw new AWeberOAuthException($response['error']['type'],
201 $response['error']['message']);
202 }
203 }
204
205 /**
206 * requiredFromResponse
207 *
208 * Enforce that all the fields in requiredFields are present and not
209 * empty in data. If a required field is empty, throw an exception.
210 *
211 * @param mixed $data Array of data
212 * @param mixed $requiredFields Array of required field names.
213 * @access protected
214 * @return void
215 */
216 protected function requiredFromResponse($data, $requiredFields) {
217 foreach ($requiredFields as $field) {
218 if (empty($data[$field])) {
219 throw new AWeberOAuthDataMissing($field);
220 }
221 }
222 }
223
224 /**
225 * get
226 *
227 * Make a get request. Used to exchange user tokens with serice provider.
228 * @param mixed $url URL to make a get request from.
229 * @param array $data Data for the request.
230 * @access protected
231 * @return void
232 */
233 protected function get($url, $data) {
234 $url = $this->_addParametersToUrl($url, $data);
235 $handle = $this->curl->init($url);
236 $resp = $this->_sendRequest($handle);
237 return $resp;
238 }
239
240 /**
241 * _addParametersToUrl
242 *
243 * Adds the parameters in associative array $data to the
244 * given URL
245 * @param String $url URL
246 * @param array $data Parameters to be added as a query string to
247 * the URL provided
248 * @access protected
249 * @return void
250 */
251 protected function _addParametersToUrl($url, $data) {
252 if (!empty($data)) {
253 if (strpos($url, '?') === false) {
254 $url .= '?'.$this->buildData($data);
255 } else {
256 $url .= '&'.$this->buildData($data);
257 }
258 }
259 return $url;
260 }
261
262 /**
263 * generateNonce
264 *
265 * Generates a 'nonce', which is a unique request id based on the
266 * timestamp. If no timestamp is provided, generate one.
267 * @param mixed $timestamp Either a timestamp (epoch seconds) or false,
268 * in which case it will generate a timestamp.
269 * @access public
270 * @return string Returns a unique nonce
271 */
272 public function generateNonce($timestamp = false) {
273 if (!$timestamp) $timestamp = $this->generateTimestamp();
274 return md5($timestamp.'-'.rand(10000,99999).'-'.uniqid());
275 }
276
277 /**
278 * generateTimestamp
279 *
280 * Generates a timestamp, in seconds
281 * @access public
282 * @return int Timestamp, in epoch seconds
283 */
284 public function generateTimestamp() {
285 return time();
286 }
287
288 /**
289 * createSignature
290 *
291 * Creates a signature on the signature base and the signature key
292 * @param mixed $sigBase Base string of data to sign
293 * @param mixed $sigKey Key to sign the data with
294 * @access public
295 * @return string The signature
296 */
297 public function createSignature($sigBase, $sigKey) {
298 switch ($this->signatureMethod) {
299 case 'HMAC-SHA1':
300 default:
301 return base64_encode(hash_hmac('sha1', $sigBase, $sigKey, true));
302 }
303 }
304
305 /**
306 * encode
307 *
308 * Short-cut for utf8_encode / rawurlencode
309 * @param mixed $data Data to encode
310 * @access protected
311 * @return void Encoded data
312 */
313 protected function encode($data) {
314 return rawurlencode(utf8_encode($data));
315 }
316
317 /**
318 * createSignatureKey
319 *
320 * Creates a key that will be used to sign our signature. Signatures
321 * are signed with the consumerSecret for this consumer application and
322 * the token secret of the user that the application is acting on behalf
323 * of.
324 * @access public
325 * @return void
326 */
327 public function createSignatureKey() {
328 return $this->consumerSecret.'&'.$this->user->tokenSecret;
329 }
330
331 /**
332 * getOAuthRequestData
333 *
334 * Get all the pre-signature, OAuth specific parameters for a request.
335 * @access public
336 * @return void
337 */
338 public function getOAuthRequestData() {
339 $token = $this->user->getHighestPriorityToken();
340 $ts = $this->generateTimestamp();
341 $nonce = $this->generateNonce($ts);
342 return array(
343 'oauth_token' => $token,
344 'oauth_consumer_key' => $this->consumerKey,
345 'oauth_version' => $this->version,
346 'oauth_timestamp' => $ts,
347 'oauth_signature_method' => $this->signatureMethod,
348 'oauth_nonce' => $nonce);
349 }
350
351
352 /**
353 * mergeOAuthData
354 *
355 * @param mixed $requestData
356 * @access public
357 * @return void
358 */
359 public function mergeOAuthData($requestData) {
360 $oauthData = $this->getOAuthRequestData();
361 return array_merge($requestData, $oauthData);
362 }
363
364 /**
365 * createSignatureBase
366 *
367 * @param mixed $method String name of HTTP method, such as "GET"
368 * @param mixed $url URL where this request will go
369 * @param mixed $data Array of params for this request. This should
370 * include ALL oauth properties except for the signature.
371 * @access public
372 * @return void
373 */
374 public function createSignatureBase($method, $url, $data) {
375 $method = $this->encode(strtoupper($method));
376 $query = parse_url($url, PHP_URL_QUERY);
377 if ($query) {
378 $parts = explode('?', $url, 2);
379 $url = array_shift($parts);
380 $items = explode('&', $query);
381 foreach ($items as $item) {
382 list($key, $value) = explode('=', $item);
383 $data[rawurldecode($key)] = rawurldecode($value);
384 }
385 }
386 $url = $this->encode($url);
387 $data = $this->encode($this->collapseDataForSignature($data));
388 return $method.'&'.$url.'&'.$data;
389 }
390
391 /**
392 * collapseDataForSignature
393 *
394 * Turns an array of request data into a string, as used by the oauth
395 * signature
396 * @param mixed $data
397 * @access public
398 * @return void
399 */
400 public function collapseDataForSignature($data) {
401 ksort($data);
402 $collapse = '';
403 foreach ($data as $key => $val) {
404 if (!empty($collapse)) $collapse .= '&';
405 $collapse .= $key.'='.$this->encode($val);
406 }
407 return $collapse;
408 }
409
410 /**
411 * signRequest
412 *
413 * Signs the request.
414 *
415 * @param mixed $method HTTP method
416 * @param mixed $url URL for the request
417 * @param mixed $data The data to be signed
418 * @access public
419 * @return array The data, with the signature.
420 */
421 public function signRequest($method, $url, $data) {
422 $base = $this->createSignatureBase($method, $url, $data);
423 $key = $this->createSignatureKey();
424 $data['oauth_signature'] = $this->createSignature($base, $key);
425 ksort($data);
426 return $data;
427 }
428
429
430 /**
431 * makeRequest
432 *
433 * Public facing function to make a request
434 *
435 * @param mixed $method
436 * @param mixed $url - Reserved characters in query params MUST be escaped
437 * @param mixed $data - Reserved characters in values MUST NOT be escaped
438 * @access public
439 * @return void
440 */
441 public function makeRequest($method, $url, $data=array()) {
442
443 if ($this->debug) echo "\n** {$method}: $url\n";
444
445 switch (strtoupper($method)) {
446 case 'POST':
447 $oauth = $this->prepareRequest($method, $url, $data);
448 $resp = $this->post($url, $oauth);
449 break;
450
451 case 'GET':
452 $oauth = $this->prepareRequest($method, $url, $data);
453 $resp = $this->get($url, $oauth, $data);
454 break;
455
456 case 'DELETE':
457 $oauth = $this->prepareRequest($method, $url, $data);
458 $resp = $this->delete($url, $oauth);
459 break;
460
461 case 'PATCH':
462 $oauth = $this->prepareRequest($method, $url, array());
463 $resp = $this->patch($url, $oauth, $data);
464 break;
465 }
466
467 // enable debug output
468 if ($this->debug) {
469 echo "<pre>";
470 print_r($oauth);
471 echo " --> Status: {$resp->headers['Status-Code']}\n";
472 echo " --> Body: {$resp->body}";
473 echo "</pre>";
474 }
475
476 if (!$resp) {
477 $msg = 'Unable to connect to the AWeber API. (' . $this->error . ')';
478 $error = array('message' => $msg, 'type' => 'APIUnreachableError',
479 'documentation_url' => 'https://labs.aweber.com/docs/troubleshooting');
480 throw new AWeberAPIException($error, $url);
481 }
482
483 if($resp->headers['Status-Code'] >= 400) {
484 $data = json_decode($resp->body, true);
485 throw new AWeberAPIException($data['error'], $url);
486 }
487
488 return $resp;
489 }
490
491 /**
492 * put
493 *
494 * Prepare an OAuth put method.
495 *
496 * @param mixed $url URL where we are making the request to
497 * @param mixed $data Data that is used to make the request
498 * @access protected
499 * @return void
500 */
501 protected function patch($url, $oauth, $data) {
502 $url = $this->_addParametersToUrl($url, $oauth);
503 $handle = $this->curl->init($url);
504 $this->curl->setopt($handle, CURLOPT_CUSTOMREQUEST, 'PATCH');
505 $this->curl->setopt($handle, CURLOPT_POSTFIELDS, json_encode($data));
506 $resp = $this->_sendRequest($handle, array('Expect:', 'Content-Type: application/json'));
507 return $resp;
508 }
509
510 /**
511 * post
512 *
513 * Prepare an OAuth post method.
514 *
515 * @param mixed $url URL where we are making the request to
516 * @param mixed $data Data that is used to make the request
517 * @access protected
518 * @return void
519 */
520 protected function post($url, $oauth) {
521 $handle = $this->curl->init($url);
522 $postData = $this->buildData($oauth);
523 $this->curl->setopt($handle, CURLOPT_POST, true);
524 $this->curl->setopt($handle, CURLOPT_POSTFIELDS, $postData);
525 $resp = $this->_sendRequest($handle);
526 return $resp;
527 }
528
529 /**
530 * delete
531 *
532 * Makes a DELETE request
533 * @param mixed $url URL where we are making the request to
534 * @param mixed $data Data that is used in the request
535 * @access protected
536 * @return void
537 */
538 protected function delete($url, $data) {
539 $url = $this->_addParametersToUrl($url, $data);
540 $handle = $this->curl->init($url);
541 $this->curl->setopt($handle, CURLOPT_CUSTOMREQUEST, 'DELETE');
542 $resp = $this->_sendRequest($handle);
543 return $resp;
544 }
545
546 /**
547 * buildData
548 *
549 * Creates a string of data for either post or get requests.
550 * @param mixed $data Array of key value pairs
551 * @access public
552 * @return void
553 */
554 public function buildData($data) {
555 ksort($data);
556 $params = array();
557 foreach ($data as $key => $value) {
558 $params[] = $key.'='.$this->encode($value);
559 }
560 return implode('&', $params);
561 }
562
563 /**
564 * _sendRequest
565 *
566 * Actually makes a request.
567 * @param mixed $handle Curl handle
568 * @param array $headers Additional headers needed for request
569 * @access private
570 * @return void
571 */
572 private function _sendRequest($handle, $headers = array('Expect:')) {
573 $this->curl->setopt($handle, CURLOPT_RETURNTRANSFER, true);
574 $this->curl->setopt($handle, CURLOPT_HEADER, true);
575 $this->curl->setopt($handle, CURLOPT_HTTPHEADER, $headers);
576 $this->curl->setopt($handle, CURLOPT_USERAGENT, $this->userAgent);
577 $this->curl->setopt($handle, CURLOPT_SSL_VERIFYPEER, TRUE);
578 $this->curl->setopt($handle, CURLOPT_VERBOSE, FALSE);
579 $this->curl->setopt($handle, CURLOPT_CONNECTTIMEOUT, 10);
580 $this->curl->setopt($handle, CURLOPT_TIMEOUT, 90);
581 $resp = $this->curl->execute($handle);
582 if ($resp) {
583 return new CurlResponse($resp);
584 }
585 $this->error = $this->curl->errno($handle) . ' - ' .
586 $this->curl->error($handle);
587 return false;
588 }
589
590 /**
591 * prepareRequest
592 *
593 * @param mixed $method HTTP method
594 * @param mixed $url URL for the request
595 * @param mixed $data The data to generate oauth data and be signed
596 * @access public
597 * @return void The data, with all its OAuth variables and signature
598 */
599 public function prepareRequest($method, $url, $data) {
600 $data = $this->mergeOAuthData($data);
601 $data = $this->signRequest($method, $url, $data);
602 return $data;
603 }
604
605 /**
606 * parseResponse
607 *
608 * Parses the body of the response into an array
609 * @param mixed $string The body of a response
610 * @access public
611 * @return void
612 */
613 public function parseResponse($resp) {
614 $data = array();
615
616 if (!$resp) { return $data; }
617 if (empty($resp)) { return $data; }
618 if (empty($resp->body)) { return $data; }
619
620 switch ($this->format) {
621 case 'json':
622 $data = json_decode($resp->body);
623 break;
624 default:
625 parse_str($resp->body, $data);
626 }
627 $this->parseAsError($data);
628 return $data;
629 }
630
631 }
632
633 /**
634 * OAuthUser
635 *
636 * Simple data class representing the user in an OAuth application.
637 * @package
638 * @version $id$
639 */
640 class OAuthUser {
641
642 public $authorizedToken = false;
643 public $requestToken = false;
644 public $verifier = false;
645 public $tokenSecret = false;
646 public $accessToken = false;
647
648 /**
649 * isAuthorized
650 *
651 * Checks if this user is authorized.
652 * @access public
653 * @return void
654 */
655 public function isAuthorized() {
656 if (empty($this->authorizedToken) && empty($this->accessToken)) {
657 return false;
658 }
659 return true;
660 }
661
662
663 /**
664 * getHighestPriorityToken
665 *
666 * Returns highest priority token - used to define authorization
667 * state for a given OAuthUser
668 * @access public
669 * @return void
670 */
671 public function getHighestPriorityToken() {
672 if (!empty($this->accessToken)) return $this->accessToken;
673 if (!empty($this->authorizedToken)) return $this->authorizedToken;
674 if (!empty($this->requestToken)) return $this->requestToken;
675
676 // Return no token, new user
677 return '';
678 }
679
680 }
681