| 1 |
<?php |
| 2 |
/* |
| 3 |
PowerPressAuth library for WordPress (copied & pasted from Network plugin) |
| 4 |
|
| 5 |
If curl is enabled, the WordPress functions are not used, allowing this class to work without WordPress |
| 6 |
*/ |
| 7 |
|
| 8 |
|
| 9 |
class PowerPressAuth { |
| 10 |
|
| 11 |
// Error handling |
| 12 |
var $error = ''; |
| 13 |
var $errorCode = 0; |
| 14 |
|
| 15 |
// API call URLs, can loop through for failures |
| 16 |
var $apiUrl = array('https://api.blubrry.com/'); |
| 17 |
var $apiUrlIndex = 0; |
| 18 |
|
| 19 |
function __construct() { |
| 20 |
|
| 21 |
if( defined('POWERPRESS_BLUBRRY_API_URL') ) { |
| 22 |
if( strstr(POWERPRESS_BLUBRRY_API_URL, 'http://api.blubrry.com') == false ) // If not the default |
| 23 |
{ |
| 24 |
$this->apiUrl = explode(';', POWERPRESS_BLUBRRY_API_URL); |
| 25 |
} |
| 26 |
else |
| 27 |
{ |
| 28 |
$this->apiUrl[] = 'https://api.blubrry.com/'; // Use secure URL first when possible |
| 29 |
$this->apiUrl[] = 'http://api.blubrry.net/'; |
| 30 |
$this->apiUrl[] = 'http://api.blubrry.com/'; |
| 31 |
} |
| 32 |
} |
| 33 |
} |
| 34 |
|
| 35 |
function getApiUrl() |
| 36 |
{ |
| 37 |
return $this->apiUrl[ $this->apiUrlIndex ]; |
| 38 |
} |
| 39 |
|
| 40 |
function getDebugInfo() { |
| 41 |
$str = ''; |
| 42 |
$str .= "API URLs:<br>\n"; |
| 43 |
$str .= "<pre>". print_r($this->apiUrl, true) . "</pre>\n"; |
| 44 |
return $str; |
| 45 |
} |
| 46 |
|
| 47 |
function getLastError() { |
| 48 |
return $this->error; |
| 49 |
} |
| 50 |
|
| 51 |
function getLastErrorCode() { |
| 52 |
return $this->errorCode; |
| 53 |
} |
| 54 |
|
| 55 |
function setApiUrl($url) { |
| 56 |
$this->apiUrlIndex = 0; |
| 57 |
$this->apiUrl = array($url); |
| 58 |
} |
| 59 |
|
| 60 |
|
| 61 |
function getTemporaryCredentials() |
| 62 |
{ |
| 63 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . "client/temporary?cache=" . md5( rand(0, 999) . time() ); |
| 64 |
$return = $this->_makeApiCall($requestUrl); |
| 65 |
while( $return === false && $this->_retryApiUrl() ) { |
| 66 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . "client/temporary?cache=" . md5( rand(0, 999) . time() ); |
| 67 |
$return = $this->_makeApiCall($requestUrl); |
| 68 |
} |
| 69 |
return $return; |
| 70 |
} |
| 71 |
|
| 72 |
function issueClient($code, $clientId, $clientSecret, $redirectUri = '') |
| 73 |
{ |
| 74 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . 'client/issue?client_id=' . urlencode($clientId) . '&client_secret=' . urlencode($clientSecret) . '&code=' . urlencode($code) . '&redirect_uri='. urlencode($redirectUri); |
| 75 |
$return = $this->_makeApiCall($requestUrl); |
| 76 |
while( $return === false && $this->_retryApiUrl() ) { |
| 77 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . 'client/issue?client_id=' . urlencode($clientId) . '&client_secret=' . urlencode($clientSecret) . '&code=' . urlencode($code) . '&redirect_uri='. urlencode($redirectUri); |
| 78 |
$return = $this->_makeApiCall($requestUrl); |
| 79 |
} |
| 80 |
return $return; |
| 81 |
} |
| 82 |
|
| 83 |
function getAccessTokenFromCode($code, $clientId, $clientSecret, $redirectUri ='') |
| 84 |
{ |
| 85 |
$clientAuth = base64_encode("$clientId:$clientSecret"); |
| 86 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . 'oauth2/token'; |
| 87 |
$post = array(); |
| 88 |
$post['grant_type'] = 'authorization_code'; |
| 89 |
$post['code'] = $code; |
| 90 |
$post['redirect_uri'] = urlencode($redirectUri); |
| 91 |
$return = $this->_makeApiCall($requestUrl, $post, $clientAuth); |
| 92 |
while( $return === false && $this->_retryApiUrl() ) { |
| 93 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . 'oauth2/token'; |
| 94 |
$return = $this->_makeApiCall($requestUrl, $post, $clientAuth); |
| 95 |
} |
| 96 |
return $return; |
| 97 |
} |
| 98 |
|
| 99 |
function getAccessTokenFromRefreshToken($refreshToken, $clientId, $clientSecret, $redirectUri ='') |
| 100 |
{ |
| 101 |
$clientAuth = base64_encode("$clientId:$clientSecret"); |
| 102 |
$post['grant_type'] = 'refresh_token'; |
| 103 |
$post['refresh_token'] = $refreshToken; |
| 104 |
$post['redirect_uri'] = $redirectUri; |
| 105 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . 'oauth2/token'; |
| 106 |
$return = $this->_makeApiCall($requestUrl, $post, $clientAuth); |
| 107 |
while( $return === false && $this->_retryApiUrl() ) { |
| 108 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . 'oauth2/token'; |
| 109 |
$return = $this->_makeApiCall($requestUrl, $post, $clientAuth); |
| 110 |
} |
| 111 |
return $return; |
| 112 |
} |
| 113 |
|
| 114 |
function reSendVerifyEmail() { |
| 115 |
$creds = get_option('powerpress_creds'); |
| 116 |
$path = '/account/create-status?client_id=' . urlencode($creds['client_id']) . '&email=true'; |
| 117 |
return $this->api('', $path); |
| 118 |
} |
| 119 |
|
| 120 |
function checkAccountVerified() { |
| 121 |
$creds = get_option('powerpress_creds'); |
| 122 |
$accessToken = !empty($creds['access_token']) ? $creds['access_token'] : ''; |
| 123 |
$path = '/account/create-status?cache=' . md5( rand(0, 999) . time() ) . '&client_id=' . urlencode($creds['client_id']); |
| 124 |
return $this->api($accessToken, $path); |
| 125 |
} |
| 126 |
|
| 127 |
function revokeClient($accessToken, $clientID, $clientSecret) { |
| 128 |
$path = '/client/revoke?client_id=' . urlencode($clientID) . '&client_secret=' . urlencode($clientSecret); |
| 129 |
return $this->api($accessToken, $path, array('client_id' => $clientID, 'client_secret' => $clientSecret)); |
| 130 |
} |
| 131 |
|
| 132 |
function api($accessToken, $path, $post = false, $custom_request = false, $timeout = 15, $decode_json = true, $encode_json = false ) |
| 133 |
{ |
| 134 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . ltrim($path, '/'); // Make sure prefix slash is removed |
| 135 |
$return = $this->_makeApiCall($requestUrl, $post, false, $accessToken, $custom_request, $timeout, $decode_json, $encode_json); |
| 136 |
while( $return === false && $this->_retryApiUrl() ) { |
| 137 |
$requestUrl = $this->apiUrl[ $this->apiUrlIndex ] . ltrim($path, '/'); // Make sure prefix slash is removed |
| 138 |
$return = $this->_makeApiCall($requestUrl, $post, false, $accessToken, $custom_request, $timeout, $decode_json, $encode_json); |
| 139 |
} |
| 140 |
return $return; |
| 141 |
} |
| 142 |
|
| 143 |
/* |
| 144 |
* Allows us to somewhat cleanly send multi-dimensional arrays over curl (still takes some parsing API-side) |
| 145 |
* https://stackoverflow.com/questions/3772096/posting-multidimensional-array-with-php-and-curl |
| 146 |
*/ |
| 147 |
function http_build_query_for_curl( $arrays, &$new = array(), $prefix = null ) { |
| 148 |
|
| 149 |
if ( is_object( $arrays ) ) { |
| 150 |
$arrays = get_object_vars( $arrays ); |
| 151 |
} |
| 152 |
|
| 153 |
foreach ( $arrays AS $key => $value ) { |
| 154 |
$k = isset( $prefix ) ? $prefix . '[' . $key . ']' : $key; |
| 155 |
if ( is_array( $value ) OR is_object( $value ) ) { |
| 156 |
$this->http_build_query_for_curl( $value, $new, $k ); |
| 157 |
} else { |
| 158 |
$new[$k] = $value; |
| 159 |
} |
| 160 |
} |
| 161 |
} |
| 162 |
|
| 163 |
private function _makeApiCallCurl($url, $post = false, $clientCredsBase64 = false, $bearerValue = '', $custom_request = false, $timeout = 15, $decode_json = true, $encode_json = false ) { |
| 164 |
|
| 165 |
$curl = curl_init(); |
| 166 |
if ( version_compare( PHP_VERSION, '5.5.0') > 0 ) |
| 167 |
curl_reset($curl); |
| 168 |
curl_setopt($curl, CURLOPT_URL, $url); |
| 169 |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); |
| 170 |
curl_setopt($curl, CURLOPT_HEADER, 0); |
| 171 |
curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); |
| 172 |
|
| 173 |
if ( version_compare( PHP_VERSION, '5.3.0') < 0 ) |
| 174 |
{ |
| 175 |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // Follow location redirection |
| 176 |
curl_setopt($curl, CURLOPT_MAXREDIRS, 12); // Location redirection limit |
| 177 |
} |
| 178 |
else if ( !ini_get('open_basedir') ) |
| 179 |
{ |
| 180 |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // Follow location redirection |
| 181 |
curl_setopt($curl, CURLOPT_MAXREDIRS, 12); // Location redirection limit |
| 182 |
} |
| 183 |
else // open_basedir is set, bummer |
| 184 |
{ |
| 185 |
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); |
| 186 |
curl_setopt($curl, CURLOPT_MAXREDIRS, 0 ); |
| 187 |
} |
| 188 |
|
| 189 |
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 2 ); // Connect time out |
| 190 |
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); // The maximum number of seconds to execute. |
| 191 |
curl_setopt($curl, CURLOPT_USERAGENT, 'Blubrry PowerPress/'.POWERPRESS_VERSION); |
| 192 |
curl_setopt($curl, CURLOPT_FAILONERROR, false); |
| 193 |
if( preg_match('/^https:\/\//i', $url) != 0 ) |
| 194 |
{ |
| 195 |
if( file_exists(ABSPATH . WPINC . '/certificates/ca-bundle.crt') ) { |
| 196 |
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2 ); |
| 197 |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true ); |
| 198 |
curl_setopt($curl, CURLOPT_CAINFO, ABSPATH . WPINC . '/certificates/ca-bundle.crt'); |
| 199 |
} else { |
| 200 |
// Trust the SSL certs, not ideal but we don't have the bundle |
| 201 |
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); |
| 202 |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); |
| 203 |
} |
| 204 |
} |
| 205 |
// HTTP Authentication |
| 206 |
if( !empty($clientCredsBase64) ) |
| 207 |
{ |
| 208 |
curl_setopt( $curl, CURLOPT_HTTPHEADER, array('Authorization: Basic '.$clientCredsBase64) ); |
| 209 |
} else if( !empty($bearerValue) ) { |
| 210 |
curl_setopt( $curl, CURLOPT_HTTPHEADER, array('Authorization: Bearer '.$bearerValue) ); |
| 211 |
} |
| 212 |
|
| 213 |
// Handle post data |
| 214 |
if ($encode_json && is_array($post)) { |
| 215 |
$post_query = array(); |
| 216 |
$this->http_build_query_for_curl($post, $post_query); |
| 217 |
curl_setopt($curl, CURLOPT_POST, 1); |
| 218 |
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_query); |
| 219 |
} elseif( is_array($post) && count($post) > 0 ) |
| 220 |
{ |
| 221 |
$post_query = ''; |
| 222 |
foreach( $post as $name => $value ) |
| 223 |
{ |
| 224 |
if( $post_query != '' ) |
| 225 |
$post_query .= '&'; |
| 226 |
$post_query .= $name; |
| 227 |
$post_query .= '='; |
| 228 |
$post_query .= urlencode($value); |
| 229 |
} |
| 230 |
curl_setopt($curl, CURLOPT_POST, 1); |
| 231 |
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_query); |
| 232 |
} |
| 233 |
else if( $custom_request ) |
| 234 |
{ |
| 235 |
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $custom_request); |
| 236 |
} |
| 237 |
|
| 238 |
$returnedBody = curl_exec($curl); |
| 239 |
$error = curl_errno($curl); |
| 240 |
$error_msg = curl_error($curl); |
| 241 |
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); |
| 242 |
if (version_compare(PHP_VERSION, '8.0', '<')) { |
| 243 |
curl_close($curl); |
| 244 |
} else { |
| 245 |
unset($curl); |
| 246 |
} |
| 247 |
|
| 248 |
if( $error ) // Curl level error, lets deal with it... |
| 249 |
{ |
| 250 |
$this->error = $error_msg; |
| 251 |
$this->errorCode = $error; |
| 252 |
return false; |
| 253 |
} |
| 254 |
else if( $http_code > 399 ) // HTTP level error, lets record it and see if the response is what we want to use... |
| 255 |
{ |
| 256 |
$this->error = "HTTP $http_code"; |
| 257 |
$this->errorCode = $http_code; |
| 258 |
switch( $http_code ) |
| 259 |
{ |
| 260 |
case 400: $this->error .= ' '. __("Bad Request", 'powerpress'); break; |
| 261 |
case 401: $this->error .= ' '. __("Unauthorized (Check that your username and password are correct)", 'powerpress'); break; |
| 262 |
case 402: $this->error .= ' '. __("Payment Required", 'powerpress'); break; |
| 263 |
case 403: $this->error .= ' '. __("Forbidden", 'powerpress'); break; |
| 264 |
case 404: $this->error .= ' '. __("Not Found", 'powerpress'); break; |
| 265 |
} |
| 266 |
} |
| 267 |
|
| 268 |
if( !empty($returnedBody) ) { |
| 269 |
if ($decode_json) { |
| 270 |
$decoded = @json_decode($returnedBody, true); |
| 271 |
// json decode |
| 272 |
if ($decoded !== false && json_last_error() === JSON_ERROR_NONE) |
| 273 |
return $decoded; |
| 274 |
} else { |
| 275 |
return $returnedBody; |
| 276 |
} |
| 277 |
|
| 278 |
// json decode returned empty/null, capture raw body as error context |
| 279 |
$preview = substr(trim($returnedBody), 0, 200); |
| 280 |
$this->error = $this->errorCode != 0 |
| 281 |
? 'Unable to decode response.' |
| 282 |
: "Unexpected API response: {$preview}"; |
| 283 |
$this->errorCode = -1; |
| 284 |
return false; |
| 285 |
} |
| 286 |
|
| 287 |
$this->error = 'Empty response from API.'; |
| 288 |
$this->errorCode = -1; |
| 289 |
return false; |
| 290 |
} |
| 291 |
|
| 292 |
private function _makeApiCall($url, $post = false, $clientCredsBase64 = false, $bearerValue = '', $custom_request = false, $timeout = 15, $decode_json = true, $encode_json = false) { |
| 293 |
|
| 294 |
// Reset the errors |
| 295 |
$this->error = ''; |
| 296 |
$this->errorCode = 0; |
| 297 |
if( function_exists('curl_init') ) // If using CURL, better handling of errors |
| 298 |
return $this->_makeApiCallCurl($url, $post, $clientCredsBase64, $bearerValue, $custom_request, $timeout, $decode_json, $encode_json); |
| 299 |
|
| 300 |
if( !function_exists('wp_remote_post') ) { |
| 301 |
$this->error = 'WordPress or curl library required.'; |
| 302 |
$this->errorCode = -1; |
| 303 |
return false; |
| 304 |
} |
| 305 |
|
| 306 |
$options = array(); |
| 307 |
$options['timeout'] = $timeout; |
| 308 |
$options['user-agent'] = 'Blubrry PowerPress/'.POWERPRESS_VERSION; |
| 309 |
if( !empty($clientCredsBase64) ) |
| 310 |
$options['headers']['Authorization'] = 'Basic '.$clientCredsBase64; |
| 311 |
else if( !empty($bearerValue) ) |
| 312 |
$options['headers']['Authorization'] = 'Bearer '.$bearerValue; |
| 313 |
|
| 314 |
if( !empty($post) ) { |
| 315 |
if ($encode_json) { |
| 316 |
$options['body'] = json_encode($post); |
| 317 |
} else { |
| 318 |
$options['body'] = $post; |
| 319 |
} |
| 320 |
$response = wp_remote_post( $url, $options ); |
| 321 |
} else if($custom_request) { |
| 322 |
$options['method'] = $custom_request; |
| 323 |
$response = wp_remote_request($url,$options); |
| 324 |
} else |
| 325 |
{ |
| 326 |
$response = wp_remote_get( $url, $options ); |
| 327 |
} |
| 328 |
|
| 329 |
if ( is_wp_error( $response ) ) |
| 330 |
{ |
| 331 |
$this->errorCode = $response->get_error_code(); |
| 332 |
$this->error = $response->get_error_message(); |
| 333 |
return false; |
| 334 |
} |
| 335 |
|
| 336 |
if( !empty($response['body']) ) |
| 337 |
$returnedBody = $response['body']; |
| 338 |
else |
| 339 |
$returnedBody = ''; |
| 340 |
|
| 341 |
if( isset($response['response']['code']) && $response['response']['code'] > 399 ) |
| 342 |
{ |
| 343 |
$this->error = "HTTP ".$response['response']['code']; |
| 344 |
$this->errorCode = $response['response']['code']; |
| 345 |
switch( $response['response']['code'] ) |
| 346 |
{ |
| 347 |
case 400: $this->error .= ' '. __("Bad Request", 'powerpress'); break; |
| 348 |
case 401: $this->error .= ' '. __("Unauthorized (Check that your username and password are correct)", 'powerpress'); break; |
| 349 |
case 402: $this->error .= ' '. __("Payment Required", 'powerpress'); break; |
| 350 |
case 403: $this->error .= ' '. __("Forbidden", 'powerpress'); break; |
| 351 |
case 404: $this->error .= ' '. __("Not Found", 'powerpress'); break; |
| 352 |
default: $this->error .= ' '.$response['response']['message']; |
| 353 |
} |
| 354 |
} |
| 355 |
|
| 356 |
if( !empty($returnedBody) ) { |
| 357 |
if ($decode_json) { |
| 358 |
$decoded = @json_decode($returnedBody, true); |
| 359 |
if ($decoded !== false) { |
| 360 |
return $decoded; |
| 361 |
} |
| 362 |
} else { |
| 363 |
return $returnedBody; |
| 364 |
} |
| 365 |
|
| 366 |
if( $this->errorCode != 0 ) { |
| 367 |
$this->error = 'Unable to decode response.'; |
| 368 |
$this->errorCode = -1; |
| 369 |
} |
| 370 |
return false; |
| 371 |
} |
| 372 |
|
| 373 |
if( !empty($returnedBody) ) |
| 374 |
$this->error = $returnedBody; |
| 375 |
else |
| 376 |
$this->error = 'Unknown error occurred.'; |
| 377 |
$this->errorCode = -1; |
| 378 |
return false; |
| 379 |
} |
| 380 |
|
| 381 |
private function _retryApiUrl() { |
| 382 |
if( ($this->apiUrlIndex+1) < count($this->apiUrl) ) { |
| 383 |
// Retry using the next indexed API url |
| 384 |
$this->apiUrlIndex++; |
| 385 |
return true; |
| 386 |
} |
| 387 |
return false; |
| 388 |
} |
| 389 |
} // end of class |
| 390 |
|
| 391 |
// eof |