OAuth provider support with functional demo/test.

Functional OAuth provider support including data storage of client/user tokens.
Documentation for the Credential object.
Gh-77 Gh-48 Gh-78
This commit is contained in:
Jaisen Mathai 2011-09-07 02:41:30 -07:00
parent dbb27fdb13
commit 7674ba748d
11 changed files with 496 additions and 167 deletions

View file

@ -0,0 +1,43 @@
Documentation
=======================
#### OpenPhoto, a photo service for the masses
----------------------------------------
### What's a Credential object for?
The Credential object stores permissioning OAuth tokens granted by the user to various applications.
Due to the distrubuted nature of the platform the model for applications and users is flattened and not relational. For every application there will be exactly 1 user.
----------------------------------------
### Schema for a User object
{
id: (string),
name: (string),
image: (string),
client_secret: (string),
user_token: (string),
user_secret: (string),
permissions: (set),
verifier: (string),
type: (enum),
status: (bool)
}
----------------------------------------
### Schema description
* id, A (quasi-)public token which identifies the application
* name, A human readable name describing the client
* image, The base64 encoded version of a 100x100 pixel image
* client_secret, A shared secret used to verify requests originated from the application
* user_token, A (quasi-)public token which idenfies the user
* user_secret, A shared secret to verify that the request originated from the application for the user
* permissions, A set of permissions the credential has (create, read, write, delete)
* verifier, A verification string to ensure that the `oauth_callback` parameter wasn't spoofed
* type, An enumerated field specifying the type of token (unauthorized_request, request, access)
* status, Numeric representation of the status (0=deleted, 1=active)

View file

@ -0,0 +1,13 @@
<h1>Would you like ot grant <?php echo $consumer['name']; ?> access to your account?</h1>
<p>
<h2>You are providing the following permissions</h2>
<ul>
<?php foreach($consumer['permissions'] as $permission) { ?>
<li><?php echo $permission; ?></li>
<?php } ?>
</ul>
</p>
<form method="post">
<input type="hidden" name="client_key" value="<?php Utility::safe($consumer['id']); ?>">
<button type="submit">Approve</button>
</form>

View file

@ -0,0 +1,15 @@
<form method="post">
<ul>
<li>Name this app: <input type="text" name="name"></li>
<li>Permissions:
<ul>
<li><input type="checkbox" name="permissions" value="read" checked="true"> Read</li>
<li><input type="checkbox" name="permissions" value="create"> Create</li>
<li><input type="checkbox" name="permissions" value="update"> Update</li>
<li><input type="checkbox" name="permissions" value="delete"> Delete</li>
</ul>
</li>
<input type="hidden" name="oauth_callback" value="<?php Utility::safe($callback); ?>">
<button type="submit">Create</button>
</form>

View file

@ -12,6 +12,7 @@ interface DatabaseInterface
public function deletePhoto($id); public function deletePhoto($id);
public function deleteAction($id); public function deleteAction($id);
// get methods read // get methods read
public function getCredential($id);
public function getPhotoNextPrevious($id); public function getPhotoNextPrevious($id);
public function getPhoto($id); public function getPhoto($id);
public function getPhotoWithActions($id); public function getPhotoWithActions($id);
@ -20,6 +21,7 @@ interface DatabaseInterface
public function getTag($tag); public function getTag($tag);
public function getTags($filter = array()); public function getTags($filter = array());
// post methods update // post methods update
public function postCredential($id, $params);
public function postPhoto($id, $params); public function postPhoto($id, $params);
public function postUser($id, $params); public function postUser($id, $params);
public function postTag($id, $params); public function postTag($id, $params);
@ -27,6 +29,7 @@ interface DatabaseInterface
public function postTagsCounter($params); public function postTagsCounter($params);
// put methods create but do not update // put methods create but do not update
public function putAction($id, $params); public function putAction($id, $params);
public function putCredential($id, $params);
public function putPhoto($id, $params); public function putPhoto($id, $params);
public function putUser($id, $params); public function putUser($id, $params);
public function putTag($id, $params); public function putTag($id, $params);

View file

@ -49,6 +49,32 @@ class DatabaseMySql implements DatabaseInterface
return ($res == 1); return ($res == 1);
} }
/**
* Retrieve a credential with $id
*
* @param string $id ID of the credential to get
* @return mixed Array on success, FALSE on failure
*/
public function getCredential($id)
{
// TODO: fill this in Gh-78
return array();
}
/**
* Get a photo specified by $id
*
* @param string $id ID of the photo to retrieve
* @return mixed Array on success, FALSE on failure
*/
public function getPhoto($id)
{
$photo = getDatabase()->one("SELECT * FROM photo WHERE id=:id", array(':id' => $id));
if(empty($photo))
return false;
return self::normalizePhoto($photo);
}
/** /**
* Retrieve the next and previous photo surrounding photo with $id * Retrieve the next and previous photo surrounding photo with $id
* *
@ -73,20 +99,6 @@ class DatabaseMySql implements DatabaseInterface
return $ret; return $ret;
} }
/**
* Get a photo specified by $id
*
* @param string $id ID of the photo to retrieve
* @return mixed Array on success, FALSE on failure
*/
public function getPhoto($id)
{
$photo = getDatabase()->one("SELECT * FROM photo WHERE id=:id", array(':id' => $id));
if(empty($photo))
return false;
return self::normalizePhoto($photo);
}
/** /**
* Retrieve a photo from the database and include the actions on the photo. * Retrieve a photo from the database and include the actions on the photo.
* Actions are stored in a separate domain so the calls need to be made in parallel * Actions are stored in a separate domain so the calls need to be made in parallel
@ -221,6 +233,20 @@ class DatabaseMySql implements DatabaseInterface
return false; return false;
} }
/**
* Update the information for an existing credential.
* This method overwrites existing values present in $params.
*
* @param string $id ID of the credential to update.
* @param array $params Attributes to update.
* @return boolean
*/
public function postCredential($id, $params)
{
// TODO: fill this in Gh-78
return true;
}
/** /**
* Update the information for an existing photo. * Update the information for an existing photo.
* This method overwrites existing values present in $params. * This method overwrites existing values present in $params.
@ -362,6 +388,20 @@ class DatabaseMySql implements DatabaseInterface
return true; return true;
} }
/**
* Add a new credential to the database
* This method does not overwrite existing values present in $params - hence "new credential".
*
* @param string $id ID of the credential to update which is always 1.
* @param array $params Attributes to update.
* @return boolean
*/
public function putCredential($id, $params)
{
// TODO: fill this in Gh-78
return true;
}
/** /**
* Add a new photo to the database * Add a new photo to the database
* This method does not overwrite existing values present in $params - hence "new photo". * This method does not overwrite existing values present in $params - hence "new photo".

View file

@ -11,7 +11,7 @@ class DatabaseSimpleDb implements DatabaseInterface
* Member variables holding the names to the SimpleDb domains needed and the database object itself. * Member variables holding the names to the SimpleDb domains needed and the database object itself.
* @access private * @access private
*/ */
private $db, $domainAction, $domainPhoto, $domainTag, $domainUser; private $db, $domainAction, $domainCredential, $domainPhoto, $domainTag, $domainUser;
/** /**
* Constructor * Constructor
@ -23,6 +23,7 @@ class DatabaseSimpleDb implements DatabaseInterface
$this->db = new AmazonSDB(getConfig()->get('credentials')->awsKey, getConfig()->get('credentials')->awsSecret); $this->db = new AmazonSDB(getConfig()->get('credentials')->awsKey, getConfig()->get('credentials')->awsSecret);
$this->domainPhoto = getConfig()->get('aws')->simpleDbDomain; $this->domainPhoto = getConfig()->get('aws')->simpleDbDomain;
$this->domainAction = getConfig()->get('aws')->simpleDbDomain.'Action'; $this->domainAction = getConfig()->get('aws')->simpleDbDomain.'Action';
$this->domainCredential = getConfig()->get('aws')->simpleDbDomain.'Credential';
$this->domainUser = getConfig()->get('aws')->simpleDbDomain.'User'; $this->domainUser = getConfig()->get('aws')->simpleDbDomain.'User';
$this->domainTag = getConfig()->get('aws')->simpleDbDomain.'Tag'; $this->domainTag = getConfig()->get('aws')->simpleDbDomain.'Tag';
} }
@ -60,6 +61,36 @@ class DatabaseSimpleDb implements DatabaseInterface
return $res->isOK(); return $res->isOK();
} }
/**
* Retrieve a credential with $id
*
* @param string $id ID of the credential to get
* @return mixed Array on success, FALSE on failure
*/
public function getCredential($id)
{
$res = $this->db->select("SELECT * FROM `{$this->domainCredential}` WHERE itemName()='{$id}' AND status='1'", array('ConsistentRead' => 'true'));
if(isset($res->body->SelectResult->Item))
return self::normalizeCredential($res->body->SelectResult->Item);
else
return false;
}
/**
* Get a photo specified by $id
*
* @param string $id ID of the photo to retrieve
* @return mixed Array on success, FALSE on failure
*/
public function getPhoto($id)
{
$res = $this->db->select("SELECT * FROM `{$this->domainPhoto}` WHERE itemName()='{$id}'", array('ConsistentRead' => 'true'));
if(isset($res->body->SelectResult->Item))
return self::normalizePhoto($res->body->SelectResult->Item);
else
return false;
}
/** /**
* Retrieve the next and previous photo surrounding photo with $id * Retrieve the next and previous photo surrounding photo with $id
* *
@ -88,22 +119,6 @@ class DatabaseSimpleDb implements DatabaseInterface
return $ret; return $ret;
} }
/**
* Get a photo specified by $id
*
* @param string $id ID of the photo to retrieve
* @return mixed Array on success, FALSE on failure
*/
public function getPhoto($id)
{
$res = $this->db->select("SELECT * FROM `{$this->domainPhoto}` WHERE itemName()='{$id}'", array('ConsistentRead' => 'true'));
if(isset($res->body->SelectResult->Item))
return self::normalizePhoto($res->body->SelectResult->Item);
else
return false;
}
/** /**
* Retrieve a photo from the database and include the actions on the photo. * Retrieve a photo from the database and include the actions on the photo.
* Actions are stored in a separate domain so the calls need to be made in parallel * Actions are stored in a separate domain so the calls need to be made in parallel
@ -269,6 +284,20 @@ class DatabaseSimpleDb implements DatabaseInterface
return false; return false;
} }
/**
* Update the information for an existing credential.
* This method overwrites existing values present in $params.
*
* @param string $id ID of the credential to update.
* @param array $params Attributes to update.
* @return boolean
*/
public function postCredential($id, $params)
{
$res = $this->db->put_attributes($this->domainCredential, $id, $params, true);
return $res->isOK();
}
/** /**
* Update the information for an existing photo. * Update the information for an existing photo.
* This method overwrites existing values present in $params. * This method overwrites existing values present in $params.
@ -401,6 +430,20 @@ class DatabaseSimpleDb implements DatabaseInterface
return $res->isOK(); return $res->isOK();
} }
/**
* Add a new credential to the database
* This method does not overwrite existing values present in $params - hence "new credential".
*
* @param string $id ID of the credential to update which is always 1.
* @param array $params Attributes to update.
* @return boolean
*/
public function putCredential($id, $params)
{
$res = $this->db->put_attributes($this->domainCredential, $id, $params);
return $res->isOK();
}
/** /**
* Add a new photo to the database * Add a new photo to the database
* This method does not overwrite existing values present in $params - hence "new photo". * This method does not overwrite existing values present in $params - hence "new photo".
@ -461,6 +504,7 @@ class DatabaseSimpleDb implements DatabaseInterface
$queue = new CFBatchRequest(); $queue = new CFBatchRequest();
$this->db->batch($queue)->create_domain($this->domainAction); $this->db->batch($queue)->create_domain($this->domainAction);
$this->db->batch($queue)->create_domain($this->domainCredential);
$this->db->batch($queue)->create_domain($this->domainPhoto); $this->db->batch($queue)->create_domain($this->domainPhoto);
$this->db->batch($queue)->create_domain($this->domainTag); $this->db->batch($queue)->create_domain($this->domainTag);
$this->db->batch($queue)->create_domain($this->domainUser); $this->db->batch($queue)->create_domain($this->domainUser);
@ -504,6 +548,28 @@ class DatabaseSimpleDb implements DatabaseInterface
return $action; return $action;
} }
/**
* Normalizes data from simpleDb into schema definition
*
* @param SimpleXMLObject $raw An action from SimpleDb in SimpleXML.
* @return array
*/
private function normalizeCredential($raw)
{
$credential = array();
$credential['id'] = strval($raw->Name);
foreach($raw->Attribute as $item)
{
$name = (string)$item->Name;
$value = (string)$item->Value;
if($name == 'permissions')
$credential[$name] = (array)explode(',', $value);
else
$credential[$name] = $value;
}
return $credential;
}
/** /**
* Normalizes data from simpleDb into schema definition * Normalizes data from simpleDb into schema definition
* *
@ -566,6 +632,7 @@ class DatabaseSimpleDb implements DatabaseInterface
private function normalizeUser($raw) private function normalizeUser($raw)
{ {
$user = array(); $user = array();
$user['id'] = strval($raw->Name);
foreach($raw->Attribute as $item) foreach($raw->Attribute as $item)
{ {
$name = (string)$item->Name; $name = (string)$item->Name;

View file

@ -3,7 +3,9 @@ class OAuthController extends BaseController
{ {
public static function authorize() public static function authorize()
{ {
$callback = $verifier = null; // TODO require login
// TODO require SSL
$callback = null;
$separator = '?'; $separator = '?';
if(isset($_GET['oauth_callback'])) if(isset($_GET['oauth_callback']))
@ -13,55 +15,158 @@ class OAuthController extends BaseController
$separator = '&'; $separator = '&';
} }
$callback .= "{$separator}oauth_token=token&oauth_verifier=verifier"; // if an oauth_token is passed then display the approval screen else ask to create a credential
echo sprintf('<a href="%s">Click here to allow and continue</a>', $callback); if(isset($_GET['oauth_token']))
{
$consumer = getCredential()->getConsumer($_GET['oauth_token']);
if(!$consumer)
{
// TODO templatize this
echo sprintf('Could not find consumer for token %s', $_GET['oauth_token']);
}
else if($consumer['type'] != Credential::typeUnauthorizedRequest)
{
// TODO templatize this
echo sprintf('This token has been approved or is invalid %s', $_GET['oauth_token']);
}
else
{
$body = getTheme()->get('oauthApprove.php', array('consumer' => $consumer));
getTheme()->display('template.php', array('body' => $body, 'page' => 'oauth-approve'));
}
}
else
{
$body = getTheme()->get('oauthCreate.php', array('callback' => $callback));
getTheme()->display('template.php', array('body' => $body, 'page' => 'oauth-create'));
}
}
public static function authorizePost()
{
// TODO require login
// TODO require SSL
if(isset($_GET['oauth_token']) && !empty($_GET['oauth_token']))
{
$token = $_GET['oauth_token'];
// if an oauth_token exists then the user wants to approve it
// change the status from unauthorized_request to request
$token = $_GET['oauth_token'];
$res = getDb()->postCredential($token, array('type' => Credential::typeRequest));
if(!$res)
{
// TODO templatize this
echo sprintf('Could not convert this unauthorized request token to a request token %s', $_GET['oauth_token']);
die();
}
$consumer = getDb()->getCredential($token);
$callback = null;
$separator = '?';
if(isset($_GET['oauth_callback']))
{
$callback = $_GET['oauth_callback'];
if(stripos($callback, '?') !== false)
$separator = '&';
}
$callback .= "{$separator}&oauth_token={$token}&oauth_verifier={$consumer['verifier']}";
// TODO require SSL unless omited in the config
getRoute()->redirect($callback, null, true);
}
else
{
// no oauth token so this call is to create a credential
$clientToken = getCredential()->add($_POST['name'], (array)explode(',', $_POST['permissions']));
if(!$clientToken)
getLogger()->warn(sprintf('Could not add credential for: %s', json_encode($_POST)));
$callback = urlencode($_POST['oauth_callback']);
getRoute()->redirect("/v1/oauth/authorize?oauth_token={$clientToken}&oauth_callback={$callback}");
}
} }
public static function flow() public static function flow()
{ {
if(!isset($_GET['oauth_token'])) if(isset($_GET['oauth_token']))
{
$ch = curl_init('http://opme/v1/oauth/token/request');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, array());
$tok = curl_exec($ch);
curl_close($ch);
parse_str($tok);
$callback = sprintf('http://%s/v1/oauth/flow', $_SERVER['HTTP_HOST']);
echo sprintf('<a href="http://opme/v1/oauth/authorize?oauth_token=%s&oauth_callback=%s">Get request token</a>', $oauth_token, urlencode($callback));
}
else
{ {
$token = $_GET['oauth_token'];
$verifier = $_GET['oauth_verifier'];
$ch = curl_init('http://opme/v1/oauth/token/access'); $ch = curl_init('http://opme/v1/oauth/token/access');
curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, array('oauth_token' => $_GET['oauth_token'])); curl_setopt($ch, CURLOPT_POSTFIELDS, array('oauth_token' => $token, 'oauth_verifier' => $verifier));
$tok = curl_exec($ch); $tok = curl_exec($ch);
curl_close($ch); curl_close($ch);
parse_str($tok); parse_str($tok);
echo sprintf('You exchanged a request token for an access token which is (%s, %s)', $oauth_token, $oauth_token_secret); setcookie('oauth', $tok);
echo sprintf('You exchanged a request token for an access token<br><a href="?reloaded=1">Reload to make an OAuth request</a>', $oauth_token, $oauth_token_secret);
}
else if(!isset($_GET['reloaded']))
{
$callback = sprintf('http://%s/v1/oauth/flow', $_SERVER['HTTP_HOST']);
echo sprintf('<a href="http://opme/v1/oauth/authorize?oauth_callback=%s">Create a new client id</a>', urlencode($callback));
}
else
{
try {
parse_str($_COOKIE['oauth']);
$consumer = getDb()->getCredential($oauth_token);
$oauth = new OAuth($oauth_token,$oauth_token_secret,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_AUTHORIZATION);
$oauth->setToken($user_token,$user_secret);
$oauth->fetch(sprintf('http://%s/v1/oauth/test?oauth_consumer_key=%s', $_SERVER['HTTP_HOST'], $oauth_token));
$response_info = $oauth->getLastResponseInfo();
header("Content-Type: {$response_info["content_type"]}");
echo $oauth->getLastResponse();
} catch(OAuthException $E) {
echo "Exception caught!\n";
echo "Response: ". $E->lastResponse . "\n";
}
} }
} }
public static function test() public static function test()
{ {
$oauth = new Auth(); if(getCredential()->checkRequest())
$oauth->checkRequest(); {
echo "Good work! This request made a successful OAuth request.";
}
else
{
echo "Boooo!!!! The OAuth request made FAILED :(.";
}
} }
public static function tokenAccess() public static function tokenAccess()
{ {
$oauth = new Auth('token','token'); // TODO require login
echo 'oauth_token=token&oauth_token_secret=token'; // TODO require SSL
// TODO check oauth_verifier
$token = $_POST['oauth_token'];
$verifier = $_POST['oauth_verifier'];
$consumer = getDb()->getCredential($token);
if(!$consumer || $consumer['verifier'] != $verifier)
{
echo 'oauth_error=could_not_authorize';
}
else
{
getCredential()->addUserToken($consumer['id'], true);
$consumer = getDb()->getCredential($token);
echo "oauth_token={$consumer['id']}&oauth_token_secret={$consumer['client_secret']}&user_token={$consumer['user_token']}&user_secret={$consumer['user_secret']}";
}
} }
public static function tokenRequest() public static function tokenRequest()
{ {
$oauth = new Auth('token','token'); // TODO require login
// TODO require SSL
// Not yet implemented
$type = 'unauthorized'; $type = 'unauthorized';
if(isset($_GET['oauth_token'])) if(isset($_GET['oauth_token']))
{
$type = 'authorized'; $type = 'authorized';
}
echo "oauth_token=token&type={$type}"; echo "oauth_token=token&type={$type}";
} }
} }

View file

@ -30,7 +30,7 @@ require getConfig()->get('paths')->libraries . '/functions.php';
// models // models
require getConfig()->get('paths')->models . '/Utility.php'; require getConfig()->get('paths')->models . '/Utility.php';
require getConfig()->get('paths')->models . '/Auth.php'; require getConfig()->get('paths')->models . '/Credential.php';
require getConfig()->get('paths')->models . '/Action.php'; require getConfig()->get('paths')->models . '/Action.php';
require getConfig()->get('paths')->models . '/Photo.php'; require getConfig()->get('paths')->models . '/Photo.php';
require getConfig()->get('paths')->models . '/Tag.php'; require getConfig()->get('paths')->models . '/Tag.php';

View file

@ -1,112 +0,0 @@
<?php
class Auth
{
private $provider;
public function checkRequest()
{
try
{
if(!$this->initProvider())
return false;
$this->provider->consumerHandler(array($this,'getConsumer'));
$this->provider->timestampNonceHandler(array($this,'checkTimestampAndNonce'));
$this->provider->tokenHandler(array($this,'tokenHandler'));
$this->provider->setParam('__route__', null);
$this->provider->setRequestTokenPath('/v1/oauth/token/request'); // No token needed for this end point
$this->provider->checkOAuthRequest('http://opme/v1/oauth/test', OAUTH_HTTP_METHOD_GET);
return true;
}
catch(OAuthException $e)
{
getLogger()->crit(OAuthProvider::reportProblem($e));
return false;
}
}
public function getConsumer($provider)
{
// $consumer = new stdClass;
// $consumer->consumer_key = 'token';//$provider->consumer_key;
// $consumer->key_status = 0;
// $consumer->secret = 'secret';
// if($provider->consumer_key != $consumer->consumer_key)
// return OAUTH_CONSUMER_KEY_UNKNOWN;
// else if($consumer->key_status != 0) // 0 is active, 1 is throttled, 2 is blacklisted
// return OAUTH_CONSUMER_KEY_REFUSED;
$provider->consumer_secret = 'token'; //$consumer->secret;
return OAUTH_OK;
}
public function checkTimestampAndNonce($provider)
{
return OAUTH_OK;
}
public function tokenHandler($provider)
{
$provider->token_secret = 'token';
return OAUTH_OK;
}
public function generateConsumerKeyAndSecret() {
$fp = fopen('/dev/urandom','rb');
$entropy = fread($fp, 32);
fclose($fp);
// in case /dev/urandom is reusing entropy from its pool, let's add a bit more entropy
$entropy .= uniqid(mt_rand(), true);
$hash = sha1($entropy); // sha1 gives us a 40-byte hash
// The first 30 bytes should be plenty for the consumer_key
// We use the last 10 for the shared secret
return array(substr($hash,0,30),substr($hash,30,10));
}
public function getOAuthParameters()
{
// default null values
// $params = array('oauth_consumer_key' => 'token', 'oauth_token' => 'token', 'oauth_nonce' => null, 'oauth_timestamp' => null,
// 'oauth_signature_method' => null, 'oauth_signature' => null);
// fetch values from header
$headers = getallheaders();
foreach($headers as $name => $header)
{
if(stripos($name, 'authorization') === 0)
{
$parameters = explode(',', $header);
foreach($parameters as $parameter)
{
list($key, $value) = explode('=', $parameter);
if(strpos($key, 'oauth_') !== 0)
continue;
$params[$key] = urldecode(substr($value, 1, -1));
}
}
}
// override with values from GET
foreach($_GET as $key => $value)
{
if(strpos($key, 'oauth_') === 0)
$params[$key] = $value;
}
ksort($params);
print_r($params);
return $params;
}
public function initProvider()
{
if(!$this->provider)
$this->provider = new OAuthProvider($this->getOAuthParameters());
if(!$this->provider)
return false;
return true;
}
}

View file

@ -0,0 +1,154 @@
<?php
class Credential
{
const typeUnauthorizedRequest = 'unauthorized_request';
const typeRequest = 'request';
const typeAccess = 'access';
const statusInactive = '0';
const statusActive = '1';
private $provider, $consumer;
public function __construct()
{
$this->provider = new OAuthProvider($this->getOAuthParameters());
}
public function add($name, $permissions = array('read'))
{
$random = bin2hex($this->provider->generateToken(25));
$id = substr($random, 0, 30);
$params = array(
'name' => $name,
'client_secret' => substr($random, -10),
/*'user_token' => '',
'user_secret' => '',*/
'permissions' => $permissions,
'verifier' => substr($random, 30, 10),
'type' => self::typeUnauthorizedRequest,
'status' => self::statusActive
);
$res = getDb()->putCredential($id, $params);
if($res)
return $id;
return false;
}
public function addUserToken($id, $convertToAccessToken = false)
{
$random = bin2hex($this->provider->generateToken(20));
$params = array(
'user_token' => substr($random, 0, 30),
'user_secret' => substr($random, -10)
);
if($convertToAccessToken)
$params['type'] = self::typeAccess;
return getDb()->postCredential($id, $params);
}
public function checkRequest()
{
try
{
$this->provider->consumerHandler(array($this,'checkConsumer'));
$this->provider->timestampNonceHandler(array($this,'checkTimestampAndNonce'));
$this->provider->tokenHandler(array($this,'checkToken'));
$this->provider->setParam('__route__', null);
$this->provider->setRequestTokenPath('/v1/oauth/token/request'); // No token needed for this end point
$this->provider->checkOAuthRequest();
return true;
}
catch(OAuthException $e)
{
getLogger()->crit(OAuthProvider::reportProblem($e));
return false;
}
}
public function checkConsumer($provider)
{
$consumer = $this->getConsumer($provider->consumer_key);
if(!$consumer)
{
getLogger()->warn(sprintf('Could not find consumer for key %s', $provider->consumer_key));
return OAUTH_CONSUMER_KEY_UNKNOWN;
}
else if($consumer['status'] != self::statusActive)
{
getLogger()->warn(sprintf('Consumer key %s refused', $provider->consumer_key));
return OAUTH_CONSUMER_KEY_REFUSED;
}
$provider->consumer_secret = $consumer['client_secret'];
return OAUTH_OK;
}
public function checkTimestampAndNonce($provider)
{
// TODO check nonce in APC/Memcached using EpiCache.
return OAUTH_OK;
}
public function checkToken($provider)
{
$consumer = $this->getConsumer($provider->consumer_key);
if(!$consumer)
{
getLogger()->warn(sprintf('Could not find consumer for key %s', $provider->consumer_key));
return OAUTH_CONSUMER_KEY_UNKNOWN;
}
$provider->token_secret = $consumer['user_secret'];
return OAUTH_OK;
}
public function getConsumer($consumerKey)
{
if(!$this->consumer)
$this->consumer = getDb()->getCredential($consumerKey);
return $this->consumer;
}
public function getOAuthParameters()
{
$params = array();
// fetch values from header
$headers = getallheaders();
foreach($headers as $name => $header)
{
if(stripos($name, 'authorization') === 0)
{
$parameters = explode(',', $header);
foreach($parameters as $parameter)
{
list($key, $value) = explode('=', $parameter);
if(strpos($key, 'oauth_') !== 0)
continue;
$params[$key] = urldecode(substr($value, 1, -1));
}
}
}
// override with values from GET
foreach($_GET as $key => $value)
{
if(strpos($key, 'oauth_') === 0)
$params[$key] = $value;
}
ksort($params);
return $params;
}
}
function getCredential()
{
static $credential;
if(!$credential)
$credential = new Credential;
return $credential;
}

View file

@ -21,6 +21,7 @@ getRoute()->get('/tags', array('TagController', 'tags'));
// oauth request token // oauth request token
getRoute()->get('/v[1]/oauth/authorize', array('OAuthController', 'authorize')); getRoute()->get('/v[1]/oauth/authorize', array('OAuthController', 'authorize'));
getRoute()->post('/v[1]/oauth/authorize', array('OAuthController', 'authorizePost'));
getRoute()->post('/v[1]/oauth/token/access', array('OAuthController', 'tokenAccess')); getRoute()->post('/v[1]/oauth/token/access', array('OAuthController', 'tokenAccess'));
getRoute()->post('/v[1]/oauth/token/request', array('OAuthController', 'tokenRequest')); getRoute()->post('/v[1]/oauth/token/request', array('OAuthController', 'tokenRequest'));
getRoute()->get('/v[1]/oauth/test', array('OAuthController', 'test')); getRoute()->get('/v[1]/oauth/test', array('OAuthController', 'test'));