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

View file

@ -49,6 +49,32 @@ class DatabaseMySql implements DatabaseInterface
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
*
@ -73,20 +99,6 @@ class DatabaseMySql implements DatabaseInterface
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.
* 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;
}
/**
* 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.
* This method overwrites existing values present in $params.
@ -362,6 +388,20 @@ class DatabaseMySql implements DatabaseInterface
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
* 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.
* @access private
*/
private $db, $domainAction, $domainPhoto, $domainTag, $domainUser;
private $db, $domainAction, $domainCredential, $domainPhoto, $domainTag, $domainUser;
/**
* Constructor
@ -23,6 +23,7 @@ class DatabaseSimpleDb implements DatabaseInterface
$this->db = new AmazonSDB(getConfig()->get('credentials')->awsKey, getConfig()->get('credentials')->awsSecret);
$this->domainPhoto = getConfig()->get('aws')->simpleDbDomain;
$this->domainAction = getConfig()->get('aws')->simpleDbDomain.'Action';
$this->domainCredential = getConfig()->get('aws')->simpleDbDomain.'Credential';
$this->domainUser = getConfig()->get('aws')->simpleDbDomain.'User';
$this->domainTag = getConfig()->get('aws')->simpleDbDomain.'Tag';
}
@ -60,6 +61,36 @@ class DatabaseSimpleDb implements DatabaseInterface
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
*
@ -88,22 +119,6 @@ class DatabaseSimpleDb implements DatabaseInterface
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.
* 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;
}
/**
* 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.
* This method overwrites existing values present in $params.
@ -401,6 +430,20 @@ class DatabaseSimpleDb implements DatabaseInterface
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
* This method does not overwrite existing values present in $params - hence "new photo".
@ -461,6 +504,7 @@ class DatabaseSimpleDb implements DatabaseInterface
$queue = new CFBatchRequest();
$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->domainTag);
$this->db->batch($queue)->create_domain($this->domainUser);
@ -504,6 +548,28 @@ class DatabaseSimpleDb implements DatabaseInterface
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
*
@ -566,6 +632,7 @@ class DatabaseSimpleDb implements DatabaseInterface
private function normalizeUser($raw)
{
$user = array();
$user['id'] = strval($raw->Name);
foreach($raw->Attribute as $item)
{
$name = (string)$item->Name;

View file

@ -3,7 +3,9 @@ class OAuthController extends BaseController
{
public static function authorize()
{
$callback = $verifier = null;
// TODO require login
// TODO require SSL
$callback = null;
$separator = '?';
if(isset($_GET['oauth_callback']))
@ -13,55 +15,158 @@ class OAuthController extends BaseController
$separator = '&';
}
$callback .= "{$separator}oauth_token=token&oauth_verifier=verifier";
echo sprintf('<a href="%s">Click here to allow and continue</a>', $callback);
// if an oauth_token is passed then display the approval screen else ask to create a credential
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()
{
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
if(isset($_GET['oauth_token']))
{
$token = $_GET['oauth_token'];
$verifier = $_GET['oauth_verifier'];
$ch = curl_init('http://opme/v1/oauth/token/access');
curl_setopt($ch, CURLOPT_POST, 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);
curl_close($ch);
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()
{
$oauth = new Auth();
$oauth->checkRequest();
if(getCredential()->checkRequest())
{
echo "Good work! This request made a successful OAuth request.";
}
else
{
echo "Boooo!!!! The OAuth request made FAILED :(.";
}
}
public static function tokenAccess()
{
$oauth = new Auth('token','token');
echo 'oauth_token=token&oauth_token_secret=token';
// TODO require login
// 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()
{
$oauth = new Auth('token','token');
// TODO require login
// TODO require SSL
// Not yet implemented
$type = 'unauthorized';
if(isset($_GET['oauth_token']))
{
$type = 'authorized';
}
echo "oauth_token=token&type={$type}";
}
}

View file

@ -30,7 +30,7 @@ require getConfig()->get('paths')->libraries . '/functions.php';
// models
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 . '/Photo.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
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/request', array('OAuthController', 'tokenRequest'));
getRoute()->get('/v[1]/oauth/test', array('OAuthController', 'test'));