htdocs/api/index.php
<?php
require_once 'App.php';
/**
* Web API Endpoint
* @see http://code.google.com/p/bear-project/
* @see http://openid-foundation-japan.github.com/draft-ietf-oauth-v2-bearer-draft11.ja.html
*/
class Page_Api_Index extends App_Page{
/** @var array API が許可し、それぞれ対応するメソッド */
protected $_methods = array('post' => 'create', 'put' => 'update', 'get' => 'read', 'delete' => 'delete');
/** @var array API が許可する URI */
protected $_enableURIs = array('me' => array('uri' => 'Me'));
/** @var array 認証が必要な場合にレスポンスヘッダに付与される Authorization ヘッダの内容 */
protected $_authenticateHeaders = array(
'realm' => 'WebAPI OAuth 2.0 Bearer',
'scope' => null,
'error' => null,
'error_description' => null,
'error_uri' => null);
protected $_statusCode = 200;
public function onInject(){
parent::onInject();
$args = array('api' => null, 'method' => strtolower(getenv('REQUEST_METHOD')));
if(!isset($_GET['api'])){
if(isset($_SERVER['API'])){
$_GET['api'] = preg_replace('!index.php$!', null, $_SERVER['API']);
$_GET['api'] = substr($_GET['api'], 5, strlen($_GET['api']));
}
}else{
$args['api'] = $_GET['api'];
}
$args['authorization'] = getenv('REDIRECT_HTTP_AUTHORIZATION');
if(empty($args['authorization'])){
$args['authorization'] = getenv('HTTP_AUTHORIZATION');
}
if(empty($args['authorization']) && isset($_REQUEST['access_token'])){
$args['authorization'] = sprintf('Bearer %s', $_REQUEST['access_token']);
}
if($args['method'] === 'post'){
foreach($_POST as $name => $value){
$args['values'][$name] = trim($value);
}
}elseif($args['method'] === 'put' || $args['method'] === 'delete'){
$putdata = file_get_contents('php://input');
parse_str($putdata, $putdata);
foreach($putdata as $name => $value){
$args['values'][$name] = trim($value);
}
}else{
unset($_GET['api']);
$args['values'] = $_GET;
}
$this->injectArgs($args);
}
public function onInit(array $args){
foreach($this->_enableURIs as $regexp => $arg){
if(preg_match(sprintf('!^%s$!', $regexp), $args['api'], $matches)){
$arg = array_merge(array('uri' => null,
'options' => array(), 'query' => array()), $arg);
if(empty($arg['values'])){
$arg['values'] = $args['values'];
}else{
foreach($args['values'] as $name => $value){
$arg['values'][$name] = trim($value);
}
}
unset($matches[0]);
foreach($matches as $i => $value){
$value = trim($value);
if(!empty($value)){
$arg['values'][$arg['query'][$i-1]] = trim($value);
}
}
unset($arg['query']);
if(preg_match('!^Bearer ([0-9a-f]{32})$!', $args['authorization'], $token)){
$auth = Zend_Auth::getInstance();
$auth->setStorage(new App_Auth_Storage(__CLASS__));
$authAdapter = BEAR::dependency('App_Auth_Adapter_OAuth', array('token' => $token[1]));
$result = $this->_auth->authenticate($authAdapter);
if($result->isValid()){
if(method_exists($this->_resource, $this->_methods[$args['method']])){
$arg['uri'] = 'Api/'.$arg['uri'];
$ro = call_user_func(array(&$this->_resource, $this->_methods[$args['method']]), $arg);
if($ro !== false){
$ro = $ro->request();
$this->set('headers', $ro->getHeaders());
$this->set('body', $ro->getBody());
}else{
throw new App_Exception_OAuth_InsufficientScope('This request is insufficient scope', $this->_authenticateHeaders);
}
}
}else{
throw new App_Exception_OAuth_InvalidToken('The access token invalid', $this->_authenticateHeaders);
}
}else{
$this->_statusCode = 401;
}
}
}
if(empty($this->_statusCode)){
$this->end(404, 'Not found');
}
}
public function onOutput(){
$this->output('oauth', array(
'statusCode' => $this->_statusCode,
'authenticateHeaders' => $this->_authenticateHeaders
));
}
}
App_Main::run('Page_Api_Index');
App/Auth/Storage.php
<?php
class App_Auth_Storage extends Zend_Auth_Storage_Session{
private $_storage = array();
public function isEmpty(){
return !isset($this->_storage[$this->_namespace]) || is_null($this->_storage[$this->_namespace]);
}
public function read(){
return $this->_storage[$this->_namespace];
}
public function write($contents){
$this->_storage[$this->_namespace] = $contents;
}
public function clear(){
$this->_storage[$this->_namespace] = null;
}
public function __construct($namespace = self::NAMESPACE_DEFAULT, $member = self::MEMBER_DEFAULT){
$this->_namespace = $namespace;
$this->_member = $member;
}
}
App/Auth/Adapter/OAuth.php
<?php
class App_Auth_Adapter_OAuth extends BEAR_Factory implements Zend_Auth_Adapter_Interface{
public function factory(){
return $this;
}
public function authenticate(){
$user = $messages = $code = null;
if(!empty($this->_config['token'])){
$user = $this->_resource->read(array('uri' => 'Db/User/Authentication',
'values' => array('token' => $this->_config['token'])))->getBody();
if(!empty($user)){
$messages = array(__('Logined.'));
$code = Zend_Auth_Result::SUCCESS;
$user['remoteAddr'] = getenv('REMOTE_ADDR');
$user['userAgent'] = getenv('HTTP_USER_AGENT');
}else{
$code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND;
}
}else{
$code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID;
}
return new Zend_Auth_Result($code, $user, $messages);
}
}
App/Exception/OAuth/InsufficientScope.php
<?php
class App_Exception_OAuth_InsufficientScope extends BEAR_Exception{
protected $_defaultMessage = 'This request is insufficient scope.';
protected $_default = array('code' => 'insufficient_scope');
public function __construct($msg = null, array $config = array()){
if(empty($msg)){
$msg = $this->_defaultMessage;
}
$config = array_merge($this->_default, (array) $config);
header(sprintf('WWW-Authenticate: Bearer realm="%s", error="%s", error_description="%s"',
$config['realm'], $config['code'], $msg));
header('HTTP/1.1 403 Forbidden');
}
}
App/Exception/OAuth/InvalidRequest.php
<?php
class App_Exception_OAuth_InvalidRequest extends BEAR_Exception{
protected $_defaultMessage = 'URI is incorrect.';
protected $_default = array('code' => 'invalid_request');
public function __construct($msg = null, array $config = array()){
if(empty($msg)){
$msg = $this->_defaultMessage;
}
$config = array_merge($this->_default, (array) $config);
header(sprintf('WWW-Authenticate: Bearer realm="%s", error="%s", error_description="%s"',
$config['realm'], $config['code'], $msg));
header('HTTP/1.1 400 Bad Request');
}
}
App/Resource/output/oauth.php
<?php
/** OAuth ヘッダー & JSON 出力
* @param array $values 値
* @param array $options オプション
* @return BEAR_Ro
*/
function outputOAuth($values, array $options){
$app = BEAR::get('app');
$ro = BEAR::factory('App_Ro_Http');
$ro->setCode($options['statusCode']);
$headers = array();
if(in_array($options['statusCode'], array(401, 403))){
$rows = array();
foreach($options['authenticateHeaders'] as $name => $value){
if(!empty($value)){
$rows[] = sprintf('%s="%s"', $name, $value);
}
}
$headers[] = sprintf('WWW-Authenticate: Bearer %s', implode(", ", $rows));
}
$body = empty($values)? null: json_encode($values);
$ro->setBody($body);
$ro->setHeaders($headers);
return $ro;
}