Файл: aweber/aweber_api/oauth_application.php
Строк: 583
<?php
if (!class_exists('CurlResponse')) require_once('curl_response.php');
/**
* OAuthServiceProvider
*
* Represents the service provider in the OAuth authentication model.
* The class that implements the service provider will contain the
* specific knowledge about the API we are interfacing with, and
* provide useful methods for interfacing with its API.
*
* For example, an OAuthServiceProvider would know the URLs necessary
* to perform specific actions, the type of data that the API calls
* would return, and would be responsible for manipulating the results
* into a useful manner.
*
* It should be noted that the methods enforced by the OAuthServiceProvider
* interface are made so that it can interact with our OAuthApplication
* cleanly, rather than from a general use perspective, though some
* methods for those purposes do exists (such as getUserData).
*
* @package
* @version $id$
*/
interface OAuthServiceProvider {
public function getAccessTokenUrl();
public function getAuthorizeUrl();
public function getRequestTokenUrl();
public function getAuthTokenFromUrl();
public function getBaseUri();
public function getUserData();
}
/**
* OAuthApplication
*
* Base class to represent an OAuthConsumer application. This class is
* intended to be extended and modified for each ServiceProvider. Each
* OAuthServiceProvider should have a complementary OAuthApplication
*
* The OAuthApplication class should contain any details on preparing
* requires that is unique or specific to that specific service provider's
* implementation of the OAuth model.
*
* This base class is based on OAuth 1.0, designed with AWeber's implementation
* as a model. An OAuthApplication built to work with a different service
* provider (especially an OAuth2.0 Application) may alter or bypass portions
* of the logic in this class to meet the needs of the service provider it
* is designed to interface with.
*
* @package
* @version $id$
*/
class OAuthApplication implements AWeberOAuthAdapter {
public $debug = false;
public $userAgent = 'AWeber OAuth Consumer Application 1.0 - https://labs.aweber.com/';
public $format = false;
public $requiresTokenSecret = true;
public $signatureMethod = 'HMAC-SHA1';
public $version = '1.0';
/**
* @var OAuthUser User currently interacting with the service provider
*/
public $user = false;
// Data binding this OAuthApplication to the consumer application it is acting
// as a proxy for
public $consumerKey = false;
public $consumerSecret = false;
/**
* __construct
*
* Create a new OAuthApplication, based on an OAuthServiceProvider
* @access public
* @return void
*/
public function __construct($parentApp = false) {
if ($parentApp) {
if (!is_a($parentApp, 'OAuthServiceProvider')) {
throw new Exception('Parent App must be a valid OAuthServiceProvider!');
}
$this->app = $parentApp;
}
$this->user = new OAuthUser();
}
/**
* request
*
* Implemented for a standard OAuth adapter interface
* @param mixed $method
* @param mixed $uri
* @param array $data
* @param array $options
* @access public
* @return void
*/
public function request($method, $uri, $data = array(), $options = array()) {
$uri = $this->app->removeBaseUri($uri);
$url = $this->app->getBaseUri() . $uri;
# WARNING: non-primative items in data must be json serialized in GET and POST.
if ($method == 'POST' or $method == 'GET') {
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = json_encode($value);
}
}
}
$response = $this->makeRequest($method, $url, $data);
if (!empty($options['return'])) {
if ($options['return'] == 'status') {
return $response->headers['Status-Code'];
}
if ($options['return'] == 'headers') {
return $response->headers;
}
if ($options['return'] == 'integer') {
return intval($response->body);
}
}
$data = json_decode($response->body, true);
if (empty($options['allow_empty']) && !isset($data)) {
throw new AWeberResponseError($uri);
}
return $data;
}
/**
* getRequestToken
*
* Gets a new request token / secret for this user.
* @access public
* @return void
*/
public function getRequestToken($callbackUrl=false) {
$data = ($callbackUrl)? array('oauth_callback' => $callbackUrl) : array();
$resp = $this->makeRequest('POST', $this->app->getRequestTokenUrl(), $data);
$data = $this->parseResponse($resp);
$this->requiredFromResponse($data, array('oauth_token', 'oauth_token_secret'));
$this->user->requestToken = $data['oauth_token'];
$this->user->tokenSecret = $data['oauth_token_secret'];
return $data['oauth_token'];
}
/**
* getAccessToken
*
* Makes a request for access tokens. Requires that the current user has an authorized
* token and token secret.
*
* @access public
* @return void
*/
public function getAccessToken() {
$resp = $this->makeRequest('POST', $this->app->getAccessTokenUrl(),
array('oauth_verifier' => $this->user->verifier)
);
$data = $this->parseResponse($resp);
$this->requiredFromResponse($data, array('oauth_token', 'oauth_token_secret'));
if (empty($data['oauth_token'])) {
throw new AWeberOAuthDataMissing('oauth_token');
}
$this->user->accessToken = $data['oauth_token'];
$this->user->tokenSecret = $data['oauth_token_secret'];
return array($data['oauth_token'], $data['oauth_token_secret']);
}
/**
* parseAsError
*
* Checks if response is an error. If it is, raise an appropriately
* configured exception.
*
* @param mixed $response Data returned from the server, in array form
* @access public
* @throws AWeberOAuthException
* @return void
*/
public function parseAsError($response) {
if (!empty($response['error'])) {
throw new AWeberOAuthException($response['error']['type'],
$response['error']['message']);
}
}
/**
* requiredFromResponse
*
* Enforce that all the fields in requiredFields are present and not
* empty in data. If a required field is empty, throw an exception.
*
* @param mixed $data Array of data
* @param mixed $requiredFields Array of required field names.
* @access protected
* @return void
*/
protected function requiredFromResponse($data, $requiredFields) {
foreach ($requiredFields as $field) {
if (empty($data[$field])) {
throw new AWeberOAuthDataMissing($field);
}
}
}
/**
* get
*
* Make a get request. Used to exchange user tokens with serice provider.
* @param mixed $url URL to make a get request from.
* @param array $data Data for the request.
* @access protected
* @return void
*/
protected function get($url, $data) {
$url = $this->_addParametersToUrl($url, $data);
$handle = curl_init($url);
$resp = $this->_sendRequest($handle);
return $resp;
}
/**
* _addParametersToUrl
*
* Adds the parameters in associative array $data to the
* given URL
* @param String $url URL
* @param array $data Parameters to be added as a query string to
* the URL provided
* @access protected
* @return void
*/
protected function _addParametersToUrl($url, $data) {
if (!empty($data)) {
if (strpos($url, '?') === false) {
$url .= '?'.$this->buildData($data);
} else {
$url .= '&'.$this->buildData($data);
}
}
return $url;
}
/**
* generateNonce
*
* Generates a 'nonce', which is a unique request id based on the
* timestamp. If no timestamp is provided, generate one.
* @param mixed $timestamp Either a timestamp (epoch seconds) or false,
* in which case it will generate a timestamp.
* @access public
* @return string Returns a unique nonce
*/
public function generateNonce($timestamp = false) {
if (!$timestamp) $timestamp = $this->generateTimestamp();
return md5($timestamp.'-'.rand(10000,99999).'-'.uniqid());
}
/**
* generateTimestamp
*
* Generates a timestamp, in seconds
* @access public
* @return int Timestamp, in epoch seconds
*/
public function generateTimestamp() {
return time();
}
/**
* createSignature
*
* Creates a signature on the signature base and the signature key
* @param mixed $sigBase Base string of data to sign
* @param mixed $sigKey Key to sign the data with
* @access public
* @return string The signature
*/
public function createSignature($sigBase, $sigKey) {
switch ($this->signatureMethod) {
case 'HMAC-SHA1':
default:
return base64_encode(hash_hmac('sha1', $sigBase, $sigKey, true));
}
}
/**
* encode
*
* Short-cut for utf8_encode / rawurlencode
* @param mixed $data Data to encode
* @access protected
* @return void Encoded data
*/
protected function encode($data) {
return rawurlencode(utf8_encode($data));
}
/**
* createSignatureKey
*
* Creates a key that will be used to sign our signature. Signatures
* are signed with the consumerSecret for this consumer application and
* the token secret of the user that the application is acting on behalf
* of.
* @access public
* @return void
*/
public function createSignatureKey() {
return $this->consumerSecret.'&'.$this->user->tokenSecret;
}
/**
* getOAuthRequestData
*
* Get all the pre-signature, OAuth specific parameters for a request.
* @access public
* @return void
*/
public function getOAuthRequestData() {
$token = $this->user->getHighestPriorityToken();
$ts = $this->generateTimestamp();
$nonce = $this->generateNonce($ts);
return array(
'oauth_token' => $token,
'oauth_consumer_key' => $this->consumerKey,
'oauth_version' => $this->version,
'oauth_timestamp' => $ts,
'oauth_signature_method' => $this->signatureMethod,
'oauth_nonce' => $nonce);
}
/**
* mergeOAuthData
*
* @param mixed $requestData
* @access public
* @return void
*/
public function mergeOAuthData($requestData) {
$oauthData = $this->getOAuthRequestData();
return array_merge($requestData, $oauthData);
}
/**
* createSignatureBase
*
* @param mixed $method String name of HTTP method, such as "GET"
* @param mixed $url URL where this request will go
* @param mixed $data Array of params for this request. This should
* include ALL oauth properties except for the signature.
* @access public
* @return void
*/
public function createSignatureBase($method, $url, $data) {
$method = $this->encode(strtoupper($method));
$query = parse_url($url, PHP_URL_QUERY);
if ($query) {
$url = array_shift(explode('?', $url, 2));
$items = explode('&', $query);
foreach ($items as $item) {
list($key, $value) = explode('=', $item);
$data[$key] = $value;
}
}
$url = $this->encode($url);
$data = $this->encode($this->collapseDataForSignature($data));
return $method.'&'.$url.'&'.$data;
}
/**
* collapseDataForSignature
*
* Turns an array of request data into a string, as used by the oauth
* signature
* @param mixed $data
* @access public
* @return void
*/
public function collapseDataForSignature($data) {
ksort($data);
$collapse = '';
foreach ($data as $key => $val) {
if (!empty($collapse)) $collapse .= '&';
$collapse .= $key.'='.$this->encode($val);
}
return $collapse;
}
/**
* signRequest
*
* Signs the request.
*
* @param mixed $method HTTP method
* @param mixed $url URL for the request
* @param mixed $data The data to be signed
* @access public
* @return array The data, with the signature.
*/
public function signRequest($method, $url, $data) {
$base = $this->createSignatureBase($method, $url, $data);
$key = $this->createSignatureKey();
$data['oauth_signature'] = $this->createSignature($base, $key);
ksort($data);
return $data;
}
/**
* makeRequest
*
* Public facing function to make a request
* @param mixed $method
* @param mixed $url
* @param mixed $data
* @access public
* @return void
*/
public function makeRequest($method, $url, $data=array()) {
if ($this->debug) echo "n** {$method}: $urln";
switch (strtoupper($method)) {
case 'POST':
$oauth = $this->prepareRequest($method, $url, $data);
$resp = $this->post($url, $oauth);
break;
case 'GET':
$oauth = $this->prepareRequest($method, $url, $data);
$resp = $this->get($url, $oauth, $data);
break;
case 'DELETE':
$oauth = $this->prepareRequest($method, $url, $data);
$resp = $this->delete($url, $oauth);
break;
case 'PATCH':
$oauth = $this->prepareRequest($method, $url, array());
$resp = $this->patch($url, $oauth, $data);
break;
}
// enable debug output
if ($this->debug) {
echo "<pre>";
print_r($oauth);
echo " --> Status: {$resp->headers['Status-Code']}n";
echo " --> Body: {$resp->body}";
echo "</pre>";
}
if (!$resp) {
$msg = 'Unable to connect to the AWeber API. (' . $this->error . ')';
$error = array('message' => $msg, 'type' => 'APIUnreachableError',
'documentation_url' => 'https://labs.aweber.com/docs/troubleshooting');
throw new AWeberAPIException($error, $url);
}
if($resp->headers['Status-Code'] >= 400) {
$data = json_decode($resp->body, true);
throw new AWeberAPIException($data['error'], $url);
}
return $resp;
}
/**
* put
*
* Prepare an OAuth put method.
*
* @param mixed $url URL where we are making the request to
* @param mixed $data Data that is used to make the request
* @access private
* @return void
*/
protected function patch($url, $oauth, $data) {
$url = $this->_addParametersToUrl($url, $oauth);
$handle = curl_init($url);
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($data));
$resp = $this->_sendRequest($handle, array('Expect:', 'Content-Type: application/json'));
return $resp;
}
/**
* post
*
* Prepare an OAuth post method.
*
* @param mixed $url URL where we are making the request to
* @param mixed $data Data that is used to make the request
* @access private
* @return void
*/
protected function post($url, $oauth) {
$handle = curl_init($url);
$postData = $this->buildData($oauth);
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, $postData);
$resp = $this->_sendRequest($handle);
return $resp;
}
/**
* delete
*
* Makes a DELETE request
* @param mixed $url URL where we are making the request to
* @param mixed $data Data that is used in the request
* @access protected
* @return void
*/
protected function delete($url, $data) {
$url = $this->_addParametersToUrl($url, $data);
$handle = curl_init($url);
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, 'DELETE');
$resp = $this->_sendRequest($handle);
return $resp;
}
/**
* buildData
*
* Creates a string of data for either post or get requests.
* @param mixed $data Array of key value pairs
* @access public
* @return void
*/
public function buildData($data) {
ksort($data);
$params = array();
foreach ($data as $key => $value) {
$params[] = $key.'='.$this->encode($value);
}
return implode('&', $params);
}
/**
* _sendRequest
*
* Actually makes a request.
* @param mixed $handle Curl handle
* @param array $headers Additional headers needed for request
* @access private
* @return void
*/
private function _sendRequest($handle, $headers = array('Expect:')) {
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HEADER, true);
curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
curl_setopt($handle, CURLOPT_USERAGENT, $this->userAgent);
curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($handle, CURLOPT_VERBOSE, FALSE);
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($handle, CURLOPT_TIMEOUT, 90);
$resp = curl_exec($handle);
if ($resp) {
return new CurlResponse($resp);
}
$this->error = curl_errno($handle).' - '.curl_error($handle);
return false;
}
/**
* prepareRequest
*
* @param mixed $method HTTP method
* @param mixed $url URL for the request
* @param mixed $data The data to generate oauth data and be signed
* @access public
* @return void The data, with all its OAuth variables and signature
*/
public function prepareRequest($method, $url, $data) {
$data = $this->mergeOAuthData($data);
$data = $this->signRequest($method, $url, $data);
return $data;
}
/**
* parseResponse
*
* Parses the body of the response into an array
* @param mixed $string The body of a response
* @access public
* @return void
*/
public function parseResponse($resp) {
$data = array();
if (!$resp) { return $data; }
if (empty($resp)) { return $data; }
if (empty($resp->body)) { return $data; }
switch ($this->format) {
case 'json':
$data = json_decode($resp->body);
break;
default:
parse_str($resp->body, $data);
}
$this->parseAsError($data);
return $data;
}
}
/**
* OAuthUser
*
* Simple data class representing the user in an OAuth application.
* @package
* @version $id$
*/
class OAuthUser {
public $authorizedToken = false;
public $requestToken = false;
public $verifier = false;
public $tokenSecret = false;
public $accessToken = false;
/**
* isAuthorized
*
* Checks if this user is authorized.
* @access public
* @return void
*/
public function isAuthorized() {
if (empty($this->authorizedToken) && empty($this->accessToken)) {
return false;
}
return true;
}
/**
* getHighestPriorityToken
*
* Returns highest priority token - used to define authorization
* state for a given OAuthUser
* @access public
* @return void
*/
public function getHighestPriorityToken() {
if (!empty($this->accessToken)) return $this->accessToken;
if (!empty($this->authorizedToken)) return $this->authorizedToken;
if (!empty($this->requestToken)) return $this->requestToken;
// Return no token, new user
return '';
}
}
?>