Changeset View
Changeset View
Standalone View
Standalone View
externals/twilio-php/Services/Twilio/Capability.php
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
<?php | |||||
/** | |||||
* Twilio Capability Token generator | |||||
* | |||||
* @category Services | |||||
* @package Services_Twilio | |||||
* @author Jeff Lindsay <jeff.lindsay@twilio.com> | |||||
* @license http://creativecommons.org/licenses/MIT/ MIT | |||||
*/ | |||||
class Services_Twilio_Capability | |||||
{ | |||||
public $accountSid; | |||||
public $authToken; | |||||
public $scopes; | |||||
/** | |||||
* Create a new TwilioCapability with zero permissions. Next steps are to | |||||
* grant access to resources by configuring this token through the | |||||
* functions allowXXXX. | |||||
* | |||||
* @param $accountSid the account sid to which this token is granted access | |||||
* @param $authToken the secret key used to sign the token. Note, this auth | |||||
* token is not visible to the user of the token. | |||||
*/ | |||||
public function __construct($accountSid, $authToken) | |||||
{ | |||||
$this->accountSid = $accountSid; | |||||
$this->authToken = $authToken; | |||||
$this->scopes = array(); | |||||
$this->clientName = false; | |||||
} | |||||
/** | |||||
* If the user of this token should be allowed to accept incoming | |||||
* connections then configure the TwilioCapability through this method and | |||||
* specify the client name. | |||||
* | |||||
* @param $clientName | |||||
*/ | |||||
public function allowClientIncoming($clientName) | |||||
{ | |||||
// clientName must be a non-zero length alphanumeric string | |||||
if (preg_match('/\W/', $clientName)) { | |||||
throw new InvalidArgumentException( | |||||
'Only alphanumeric characters allowed in client name.'); | |||||
} | |||||
if (strlen($clientName) == 0) { | |||||
throw new InvalidArgumentException( | |||||
'Client name must not be a zero length string.'); | |||||
} | |||||
$this->clientName = $clientName; | |||||
$this->allow('client', 'incoming', | |||||
array('clientName' => $clientName)); | |||||
} | |||||
/** | |||||
* Allow the user of this token to make outgoing connections. | |||||
* | |||||
* @param $appSid the application to which this token grants access | |||||
* @param $appParams signed parameters that the user of this token cannot | |||||
* overwrite. | |||||
*/ | |||||
public function allowClientOutgoing($appSid, array $appParams=array()) | |||||
{ | |||||
$this->allow('client', 'outgoing', array( | |||||
'appSid' => $appSid, | |||||
'appParams' => http_build_query($appParams, '', '&'))); | |||||
} | |||||
/** | |||||
* Allow the user of this token to access their event stream. | |||||
* | |||||
* @param $filters key/value filters to apply to the event stream | |||||
*/ | |||||
public function allowEventStream(array $filters=array()) | |||||
{ | |||||
$this->allow('stream', 'subscribe', array( | |||||
'path' => '/2010-04-01/Events', | |||||
'params' => http_build_query($filters, '', '&'), | |||||
)); | |||||
} | |||||
/** | |||||
* Generates a new token based on the credentials and permissions that | |||||
* previously has been granted to this token. | |||||
* | |||||
* @param $ttl the expiration time of the token (in seconds). Default | |||||
* value is 3600 (1hr) | |||||
* @return the newly generated token that is valid for $ttl seconds | |||||
*/ | |||||
public function generateToken($ttl = 3600) | |||||
{ | |||||
$payload = array( | |||||
'scope' => array(), | |||||
'iss' => $this->accountSid, | |||||
'exp' => time() + $ttl, | |||||
); | |||||
$scopeStrings = array(); | |||||
foreach ($this->scopes as $scope) { | |||||
if ($scope->privilege == "outgoing" && $this->clientName) | |||||
$scope->params["clientName"] = $this->clientName; | |||||
$scopeStrings[] = $scope->toString(); | |||||
} | |||||
$payload['scope'] = implode(' ', $scopeStrings); | |||||
return JWT::encode($payload, $this->authToken, 'HS256'); | |||||
} | |||||
protected function allow($service, $privilege, $params) { | |||||
$this->scopes[] = new ScopeURI($service, $privilege, $params); | |||||
} | |||||
} | |||||
/** | |||||
* Scope URI implementation | |||||
* | |||||
* Simple way to represent configurable privileges in an OAuth | |||||
* friendly way. For our case, they look like this: | |||||
* | |||||
* scope:<service>:<privilege>?<params> | |||||
* | |||||
* For example: | |||||
* scope:client:incoming?name=jonas | |||||
* | |||||
* @author Jeff Lindsay <jeff.lindsay@twilio.com> | |||||
*/ | |||||
class ScopeURI | |||||
{ | |||||
public $service; | |||||
public $privilege; | |||||
public $params; | |||||
public function __construct($service, $privilege, $params = array()) | |||||
{ | |||||
$this->service = $service; | |||||
$this->privilege = $privilege; | |||||
$this->params = $params; | |||||
} | |||||
public function toString() | |||||
{ | |||||
$uri = "scope:{$this->service}:{$this->privilege}"; | |||||
if (count($this->params)) { | |||||
$uri .= "?".http_build_query($this->params, '', '&'); | |||||
} | |||||
return $uri; | |||||
} | |||||
/** | |||||
* Parse a scope URI into a ScopeURI object | |||||
* | |||||
* @param string $uri The scope URI | |||||
* @return ScopeURI The parsed scope uri | |||||
*/ | |||||
public static function parse($uri) | |||||
{ | |||||
if (strpos($uri, 'scope:') !== 0) { | |||||
throw new UnexpectedValueException( | |||||
'Not a scope URI according to scheme'); | |||||
} | |||||
$parts = explode('?', $uri, 1); | |||||
$params = null; | |||||
if (count($parts) > 1) { | |||||
parse_str($parts[1], $params); | |||||
} | |||||
$parts = explode(':', $parts[0], 2); | |||||
if (count($parts) != 3) { | |||||
throw new UnexpectedValueException( | |||||
'Not enough parts for scope URI'); | |||||
} | |||||
list($scheme, $service, $privilege) = $parts; | |||||
return new ScopeURI($service, $privilege, $params); | |||||
} | |||||
} | |||||
/** | |||||
* JSON Web Token implementation | |||||
* | |||||
* Minimum implementation used by Realtime auth, based on this spec: | |||||
* http://self-issued.info/docs/draft-jones-json-web-token-01.html. | |||||
* | |||||
* @author Neuman Vong <neuman@twilio.com> | |||||
*/ | |||||
class JWT | |||||
{ | |||||
/** | |||||
* @param string $jwt The JWT | |||||
* @param string|null $key The secret key | |||||
* @param bool $verify Don't skip verification process | |||||
* | |||||
* @return object The JWT's payload as a PHP object | |||||
*/ | |||||
public static function decode($jwt, $key = null, $verify = true) | |||||
{ | |||||
$tks = explode('.', $jwt); | |||||
if (count($tks) != 3) { | |||||
throw new UnexpectedValueException('Wrong number of segments'); | |||||
} | |||||
list($headb64, $payloadb64, $cryptob64) = $tks; | |||||
if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64))) | |||||
) { | |||||
throw new UnexpectedValueException('Invalid segment encoding'); | |||||
} | |||||
if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($payloadb64)) | |||||
) { | |||||
throw new UnexpectedValueException('Invalid segment encoding'); | |||||
} | |||||
$sig = JWT::urlsafeB64Decode($cryptob64); | |||||
if ($verify) { | |||||
if (empty($header->alg)) { | |||||
throw new DomainException('Empty algorithm'); | |||||
} | |||||
if ($sig != JWT::sign("$headb64.$payloadb64", $key, $header->alg)) { | |||||
throw new UnexpectedValueException('Signature verification failed'); | |||||
} | |||||
} | |||||
return $payload; | |||||
} | |||||
/** | |||||
* @param object|array $payload PHP object or array | |||||
* @param string $key The secret key | |||||
* @param string $algo The signing algorithm | |||||
* | |||||
* @return string A JWT | |||||
*/ | |||||
public static function encode($payload, $key, $algo = 'HS256') | |||||
{ | |||||
$header = array('typ' => 'JWT', 'alg' => $algo); | |||||
$segments = array(); | |||||
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header)); | |||||
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload)); | |||||
$signing_input = implode('.', $segments); | |||||
$signature = JWT::sign($signing_input, $key, $algo); | |||||
$segments[] = JWT::urlsafeB64Encode($signature); | |||||
return implode('.', $segments); | |||||
} | |||||
/** | |||||
* @param string $msg The message to sign | |||||
* @param string $key The secret key | |||||
* @param string $method The signing algorithm | |||||
* | |||||
* @return string An encrypted message | |||||
*/ | |||||
public static function sign($msg, $key, $method = 'HS256') | |||||
{ | |||||
$methods = array( | |||||
'HS256' => 'sha256', | |||||
'HS384' => 'sha384', | |||||
'HS512' => 'sha512', | |||||
); | |||||
if (empty($methods[$method])) { | |||||
throw new DomainException('Algorithm not supported'); | |||||
} | |||||
return hash_hmac($methods[$method], $msg, $key, true); | |||||
} | |||||
/** | |||||
* @param string $input JSON string | |||||
* | |||||
* @return object Object representation of JSON string | |||||
*/ | |||||
public static function jsonDecode($input) | |||||
{ | |||||
$obj = json_decode($input); | |||||
if (function_exists('json_last_error') && $errno = json_last_error()) { | |||||
JWT::handleJsonError($errno); | |||||
} | |||||
else if ($obj === null && $input !== 'null') { | |||||
throw new DomainException('Null result with non-null input'); | |||||
} | |||||
return $obj; | |||||
} | |||||
/** | |||||
* @param object|array $input A PHP object or array | |||||
* | |||||
* @return string JSON representation of the PHP object or array | |||||
*/ | |||||
public static function jsonEncode($input) | |||||
{ | |||||
$json = json_encode($input); | |||||
if (function_exists('json_last_error') && $errno = json_last_error()) { | |||||
JWT::handleJsonError($errno); | |||||
} | |||||
else if ($json === 'null' && $input !== null) { | |||||
throw new DomainException('Null result with non-null input'); | |||||
} | |||||
return $json; | |||||
} | |||||
/** | |||||
* @param string $input A base64 encoded string | |||||
* | |||||
* @return string A decoded string | |||||
*/ | |||||
public static function urlsafeB64Decode($input) | |||||
{ | |||||
$padlen = 4 - strlen($input) % 4; | |||||
$input .= str_repeat('=', $padlen); | |||||
return base64_decode(strtr($input, '-_', '+/')); | |||||
} | |||||
/** | |||||
* @param string $input Anything really | |||||
* | |||||
* @return string The base64 encode of what you passed in | |||||
*/ | |||||
public static function urlsafeB64Encode($input) | |||||
{ | |||||
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); | |||||
} | |||||
/** | |||||
* @param int $errno An error number from json_last_error() | |||||
* | |||||
* @return void | |||||
*/ | |||||
private static function handleJsonError($errno) | |||||
{ | |||||
$messages = array( | |||||
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', | |||||
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', | |||||
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON' | |||||
); | |||||
throw new DomainException(isset($messages[$errno]) | |||||
? $messages[$errno] | |||||
: 'Unknown JSON error: ' . $errno | |||||
); | |||||
} | |||||
} |