BEAR Saturday を使った OAuth 2.0 Bearer エンドポイントの実装例

```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;
}