12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2012-07-13
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;
}
12
13
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?