Initial work adding oauth to api
This commit is contained in:
parent
df996a8b5e
commit
b06588ffa1
8 changed files with 2709 additions and 1 deletions
|
@ -2,7 +2,7 @@
|
||||||
require_once("bbcode.php");
|
require_once("bbcode.php");
|
||||||
require_once("datetime.php");
|
require_once("datetime.php");
|
||||||
require_once("conversation.php");
|
require_once("conversation.php");
|
||||||
|
require_once("oauth.php");
|
||||||
/*
|
/*
|
||||||
* Twitter-Like API
|
* Twitter-Like API
|
||||||
*
|
*
|
||||||
|
@ -1135,3 +1135,32 @@
|
||||||
}
|
}
|
||||||
api_register_func('api/direct_messages/sent','api_direct_messages_sentbox',true);
|
api_register_func('api/direct_messages/sent','api_direct_messages_sentbox',true);
|
||||||
api_register_func('api/direct_messages','api_direct_messages_inbox',true);
|
api_register_func('api/direct_messages','api_direct_messages_inbox',true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function api_oauth_request_token(&$a, $type){
|
||||||
|
try{
|
||||||
|
$oauth = new FKOAuth1();
|
||||||
|
$r = $oauth->fetch_request_token(OAuthRequest::from_request());
|
||||||
|
}catch(Exception $e){
|
||||||
|
echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme();
|
||||||
|
}
|
||||||
|
echo "oauth_token=".$r->key."&oauth_secret=".$r->secret;
|
||||||
|
killme();
|
||||||
|
}
|
||||||
|
function api_oauth_access_token(&$a, $type){
|
||||||
|
try{
|
||||||
|
$oauth = new FKOAuth1();
|
||||||
|
$r = $oauth->fetch_access_token(OAuthRequest::from_request());
|
||||||
|
}catch(Exception $e){
|
||||||
|
echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme();
|
||||||
|
}
|
||||||
|
echo "oauth_token=".$r->key."&oauth_secret=".$r->secret;
|
||||||
|
killme();
|
||||||
|
}
|
||||||
|
function api_oauth_authorize(&$a, $type){
|
||||||
|
}
|
||||||
|
api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
|
||||||
|
api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
|
||||||
|
api_register_func('api/oauth/authorize', 'api_oauth_authorize', false);
|
||||||
|
|
||||||
|
|
194
include/oauth.php
Normal file
194
include/oauth.php
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* OAuth server
|
||||||
|
* Based on oauth2-php <http://code.google.com/p/oauth2-php/>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
define('TOKEN_DURATION', 300);
|
||||||
|
|
||||||
|
require_once("library/OAuth1.php");
|
||||||
|
require_once("library/oauth2-php/lib/OAuth2.inc");
|
||||||
|
|
||||||
|
class FKOAuthDataStore extends OAuthDataStore {
|
||||||
|
function gen_token(){
|
||||||
|
return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookup_consumer($consumer_key) {
|
||||||
|
//echo "<pre>"; var_dump($consumer_key); killme();
|
||||||
|
|
||||||
|
$r = q("SELECT client_id, pw, redirect_uri FROM clients WHERE client_id='%s'",
|
||||||
|
dbesc($consumer_key)
|
||||||
|
);
|
||||||
|
if (count($r))
|
||||||
|
return new OAuthConsumer($r[0]['client_id'],$r[0]['pw'],$r[0]['redirect_uri']);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookup_token($consumer, $token_type, $token) {
|
||||||
|
//echo __file__.":".__line__."<pre>"; var_dump($consumer, $token_type, $token); killme();
|
||||||
|
$r = q("SELECT id, secret,scope, expires FROM tokens WHERE client_id='%s' AND scope='%s' AND id='%s'",
|
||||||
|
dbesc($consumer->key),
|
||||||
|
dbesc($token_type),
|
||||||
|
dbesc($token)
|
||||||
|
);
|
||||||
|
if (count($r)){
|
||||||
|
$ot=new OAuthToken($r[0]['id'],$r[0]['secret']);
|
||||||
|
$ot->scope=$r[0]['scope'];
|
||||||
|
$ot->expires = $r[0]['expires'];
|
||||||
|
return $ot;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookup_nonce($consumer, $token, $nonce, $timestamp) {
|
||||||
|
//echo __file__.":".__line__."<pre>"; var_dump($consumer,$key); killme();
|
||||||
|
$r = q("SELECT id, secret FROM tokens WHERE client_id='%s' AND id='%s' AND expires=%d",
|
||||||
|
dbesc($consumer->key),
|
||||||
|
dbesc($nonce),
|
||||||
|
intval($timestamp)
|
||||||
|
);
|
||||||
|
if (count($r))
|
||||||
|
return new OAuthToken($r[0]['id'],$r[0]['secret']);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function new_request_token($consumer, $callback = null) {
|
||||||
|
$key = $this->gen_token();
|
||||||
|
$sec = $this->gen_token();
|
||||||
|
$r = q("INSERT INTO tokens (id, secret, client_id, scope, expires) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d)",
|
||||||
|
dbesc($key),
|
||||||
|
dbesc($sec),
|
||||||
|
dbesc($consumer->key),
|
||||||
|
'request',
|
||||||
|
intval(TOKEN_DURATION));
|
||||||
|
if (!$r) return null;
|
||||||
|
return new OAuthToken($key,$sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
function new_access_token($token, $consumer, $verifier = null) {
|
||||||
|
// return a new access token attached to this consumer
|
||||||
|
// for the user associated with this token if the request token
|
||||||
|
// is authorized
|
||||||
|
// should also invalidate the request token
|
||||||
|
|
||||||
|
$ret=Null;
|
||||||
|
|
||||||
|
if (!is_null($token) && $token->expires > time()){
|
||||||
|
|
||||||
|
$key = $this->gen_token();
|
||||||
|
$sec = $this->gen_token();
|
||||||
|
$r = q("INSERT INTO tokens (id, secret, client_id, scope, expires) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d)",
|
||||||
|
dbesc($key),
|
||||||
|
dbesc($sec),
|
||||||
|
dbesc($consumer->$key),
|
||||||
|
'access',
|
||||||
|
intval(TOKEN_DURATION));
|
||||||
|
if ($r)
|
||||||
|
$ret = new OAuthToken($key,$sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
q("DELETE FROM tokens WHERE id='%s'", $token->key);
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FKOAuth1 extends OAuthServer {
|
||||||
|
function __construct() {
|
||||||
|
parent::__construct(new FKOAuthDataStore());
|
||||||
|
$this->add_signature_method(new OAuthSignatureMethod_PLAINTEXT());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FKOAuth2 extends OAuth2 {
|
||||||
|
|
||||||
|
private function db_secret($client_secret){
|
||||||
|
return hash('whirlpool',$client_secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addClient($client_id, $client_secret, $redirect_uri) {
|
||||||
|
$client_secret = $this->db_secret($client_secret);
|
||||||
|
$r = q("INSERT INTO clients (client_id, pw, redirect_uri) VALUES ('%s', '%s', '%s')",
|
||||||
|
dbesc($client_id),
|
||||||
|
dbesc($client_secret),
|
||||||
|
dbesc($redirect_uri)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkClientCredentials($client_id, $client_secret = NULL) {
|
||||||
|
$client_secret = $this->db_secret($client_secret);
|
||||||
|
|
||||||
|
$r = q("SELECT pw FROM clients WHERE client_id = '%s'",
|
||||||
|
dbesc($client_id));
|
||||||
|
|
||||||
|
if ($client_secret === NULL)
|
||||||
|
return $result !== FALSE;
|
||||||
|
|
||||||
|
return $result["client_secret"] == $client_secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRedirectUri($client_id) {
|
||||||
|
$r = q("SELECT redirect_uri FROM clients WHERE client_id = '%s'",
|
||||||
|
dbesc($client_id));
|
||||||
|
if ($r === FALSE)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return isset($r[0]["redirect_uri"]) && $r[0]["redirect_uri"] ? $r[0]["redirect_uri"] : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAccessToken($oauth_token) {
|
||||||
|
$r = q("SELECT client_id, expires, scope FROM tokens WHERE id = '%s'",
|
||||||
|
dbesc($oauth_token));
|
||||||
|
|
||||||
|
if (count($r))
|
||||||
|
return $r[0];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL) {
|
||||||
|
$r = q("INSERT INTO tokens (id, client_id, expires, scope) VALUES ('%s', '%s', %d, '%s')",
|
||||||
|
dbesc($oauth_token),
|
||||||
|
dbesc($client_id),
|
||||||
|
intval($expires),
|
||||||
|
dbesc($scope));
|
||||||
|
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSupportedGrantTypes() {
|
||||||
|
return array(
|
||||||
|
OAUTH2_GRANT_TYPE_AUTH_CODE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getAuthCode($code) {
|
||||||
|
$r = q("SELECT id, client_id, redirect_uri, expires, scope FROM auth_codes WHERE id = '%s'",
|
||||||
|
dbesc($code));
|
||||||
|
|
||||||
|
if (count($r))
|
||||||
|
return $r[0];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {
|
||||||
|
$r = q("INSERT INTO auth_codes
|
||||||
|
(id, client_id, redirect_uri, expires, scope) VALUES
|
||||||
|
('%s', '%s', '%s', %d, '%s')",
|
||||||
|
dbesc($code),
|
||||||
|
dbesc($client_id),
|
||||||
|
dbesc($redirect_uri),
|
||||||
|
intval($expires),
|
||||||
|
dbesc($scope));
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
library/oauth2-php/CHANGELOG.txt
Normal file
98
library/oauth2-php/CHANGELOG.txt
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
oauth2-php revision xxx, xxxx-xx-xx (development version)
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
oauth2-php revision 23, 2011-01-25
|
||||||
|
----------------------
|
||||||
|
* introduce Drupal style getVariable() and setVariable, replace legacy
|
||||||
|
variable get/set functions.
|
||||||
|
* remove hardcode PHP display_error and errror_reporting, as this should
|
||||||
|
be manually implement within 3rd party integration.
|
||||||
|
* make verbose error as configurable and default disable, as this should
|
||||||
|
be manually enable within 3rd party integration.
|
||||||
|
* add lib/OAuth2Client.inc and lib/OAuth2Exception.inc for client-side
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
oauth2-php revision 21, 2010-12-18
|
||||||
|
----------------------
|
||||||
|
* cleanup tabs and trailing whitespace at the end.
|
||||||
|
* remove server/examples/mongo/lib/oauth.php and
|
||||||
|
server/examples/pdo/lib/oauth.php, so only keep single copy as
|
||||||
|
lib /oauth.php.
|
||||||
|
* issue #5: Wrong variable name in get_access_token() in pdo_oatuh.php.
|
||||||
|
* issue #6: mysql_create_tables.sql should allow scope to be NULL.
|
||||||
|
* issue #7: authorize_client_response_type() is never used.
|
||||||
|
* issue #9: Change "redirect_uri" filtering from FILTER_VALIDATE_URL to
|
||||||
|
FILTER_SANITIZE_URL.
|
||||||
|
* better coding syntax for error() and callback_error().
|
||||||
|
* better pdo_oauth2.php variable naming with change to
|
||||||
|
mysql_create_tables.sql.
|
||||||
|
* change REGEX_CLIENT_ID as 3-32 characters long, so will work with md5()
|
||||||
|
result directly.
|
||||||
|
* debug linkage to oauth2.php during previous commit.
|
||||||
|
* debug redirect_uri check for AUTH_CODE_GRANT_TYPE, clone from
|
||||||
|
get_authorize_params().
|
||||||
|
* update mysql_create_tables.sql with phpmyadmin export format.
|
||||||
|
* rename library files, prepare for adding client-side implementation.
|
||||||
|
* code cleanup with indent and spacing.
|
||||||
|
* code cleanup true/false/null with TRUE/FALSE/NULL.
|
||||||
|
* rename constants with OAUTH2_ prefix, prevent 3rd party integration
|
||||||
|
conflict.
|
||||||
|
* remove HTTP 400 response constant, as useless refer to draft v10.
|
||||||
|
* merge ERROR_INVALID_CLIENT_ID and ERROR_UNAUTHORIZED_CLIENT as
|
||||||
|
OAUTH2_ERROR_INVALID_CLIENT, as refer to that of draft v9 to v10 changes.
|
||||||
|
* improve constants comment with doxygen syntax.
|
||||||
|
* update class function call naming.
|
||||||
|
* coding style clean up.
|
||||||
|
* update part of documents.
|
||||||
|
* change expirseRefreshToken() as unsetRefreshToken().
|
||||||
|
* update token and auth code generation as md5() result, simpler for manual
|
||||||
|
debug with web browser.
|
||||||
|
* update all documents.
|
||||||
|
* restructure @ingroup.
|
||||||
|
* rename checkRestrictedClientResponseTypes() as
|
||||||
|
checkRestrictedAuthResponseType().
|
||||||
|
* rename checkRestrictedClientGrantTypes() as checkRestrictedGrantType().
|
||||||
|
* rename error() as errorJsonResponse().
|
||||||
|
* rename errorCallback() as errorDoRedirectUriCallback().
|
||||||
|
* rename send401Unauthorized() as errorWWWAuthenticateResponseHeader(),
|
||||||
|
update support with different HTTP status code.
|
||||||
|
* update __construct() with array input.
|
||||||
|
* update finishClientAuthorization() with array input.
|
||||||
|
* add get/set functions for $access_token_lifetime, $auth_code_lifetime and
|
||||||
|
$refresh_token_lifetime.
|
||||||
|
* fix a lots of typos.
|
||||||
|
* document all sample server implementation.
|
||||||
|
* more documents.
|
||||||
|
* add config.doxy for doxygen default setup.
|
||||||
|
* add MIT LICENSE.txt.
|
||||||
|
* add CHANGELOG.txt.
|
||||||
|
|
||||||
|
oauth2-php revision 9, 2010-09-04
|
||||||
|
----------------------
|
||||||
|
- fixes for issues #2 and #4, updates oauth lib in the example folders to
|
||||||
|
the latest version in the 'lib' folder.
|
||||||
|
- updates server library to revision 10 of the OAuth 2.0 spec.
|
||||||
|
- adds an option for more verbose error messages to be returned in the JSON
|
||||||
|
response.
|
||||||
|
- adds method to be overridden for expiring used refresh tokens.
|
||||||
|
- fixes bug checking token expiration.
|
||||||
|
- makes some more methods protected instead of private so they can be
|
||||||
|
overridden.
|
||||||
|
- fixes issue #1 http://code.google.com/p/oauth2-php/issues/detail?id=1
|
||||||
|
|
||||||
|
oauth2-php revision 7, 2010-06-29
|
||||||
|
----------------------
|
||||||
|
- fixed mongo connection constants.
|
||||||
|
- updated store_refresh_token to include expires time.
|
||||||
|
- changed example server directory structure
|
||||||
|
- corrected "false" return result on get_stored_auth_code.
|
||||||
|
- implemented PDO example adapter.
|
||||||
|
- corrected an error in assertion grant type.
|
||||||
|
- updated for ietf draft v9:
|
||||||
|
http://tools.ietf.org/html/draft-ietf-oauth-v2-09.
|
||||||
|
- updated updated to support v9 lib.
|
||||||
|
- added mysql table creation script.
|
||||||
|
|
||||||
|
oauth2-php revision 0, 2010-06-27
|
||||||
|
----------------------
|
||||||
|
- initial commit.
|
21
library/oauth2-php/LICENSE.txt
Normal file
21
library/oauth2-php/LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010 Tim Ridgely <tim@opendining.net>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
1560
library/oauth2-php/lib/OAuth2.inc
Normal file
1560
library/oauth2-php/lib/OAuth2.inc
Normal file
|
@ -0,0 +1,1560 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mainpage
|
||||||
|
* OAuth 2.0 server in PHP, originally written for
|
||||||
|
* <a href="http://www.opendining.net/"> Open Dining</a>. Supports
|
||||||
|
* <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10">IETF draft v10</a>.
|
||||||
|
*
|
||||||
|
* Source repo has sample servers implementations for
|
||||||
|
* <a href="http://php.net/manual/en/book.pdo.php"> PHP Data Objects</a> and
|
||||||
|
* <a href="http://www.mongodb.org/">MongoDB</a>. Easily adaptable to other
|
||||||
|
* storage engines.
|
||||||
|
*
|
||||||
|
* PHP Data Objects supports a variety of databases, including MySQL,
|
||||||
|
* Microsoft SQL Server, SQLite, and Oracle, so you can try out the sample
|
||||||
|
* to see how it all works.
|
||||||
|
*
|
||||||
|
* We're expanding the wiki to include more helpful documentation, but for
|
||||||
|
* now, your best bet is to view the oauth.php source - it has lots of
|
||||||
|
* comments.
|
||||||
|
*
|
||||||
|
* @author Tim Ridgely <tim.ridgely@gmail.com>
|
||||||
|
* @author Aaron Parecki <aaron@parecki.com>
|
||||||
|
* @author Edison Wong <hswong3i@pantarei-design.com>
|
||||||
|
*
|
||||||
|
* @see http://code.google.com/p/oauth2-php/
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default duration in seconds of the access token lifetime.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME", 3600);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default duration in seconds of the authorization code lifetime.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_DEFAULT_AUTH_CODE_LIFETIME", 30);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default duration in seconds of the refresh token lifetime.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME", 1209600);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup oauth2_section_2 Client Credentials
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* When interacting with the authorization server, the client identifies
|
||||||
|
* itself using a client identifier and authenticates using a set of
|
||||||
|
* client credentials. This specification provides one mechanism for
|
||||||
|
* authenticating the client using password credentials.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to filter out the client identifier (described in Section 2 of IETF draft).
|
||||||
|
*
|
||||||
|
* IETF draft does not prescribe a format for these, however I've arbitrarily
|
||||||
|
* chosen alphanumeric strings with hyphens and underscores, 3-32 characters
|
||||||
|
* long.
|
||||||
|
*
|
||||||
|
* Feel free to change.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_CLIENT_ID_REGEXP", "/^[a-z0-9-_]{3,32}$/i");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup oauth2_section_3 Obtaining End-User Authorization
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* When the client interacts with an end-user, the end-user MUST first
|
||||||
|
* grant the client authorization to access its protected resources.
|
||||||
|
* Once obtained, the end-user access grant is expressed as an
|
||||||
|
* authorization code which the client uses to obtain an access token.
|
||||||
|
* To obtain an end-user authorization, the client sends the end-user to
|
||||||
|
* the end-user authorization endpoint.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "token" authorization response type.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN", "token");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "code" authorization response type.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE", "code");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "code-and-token" authorization response type.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN", "code-and-token");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to filter out the authorization response type.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_AUTH_RESPONSE_TYPE_REGEXP", "/^(token|code|code-and-token)$/");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup oauth2_section_4 Obtaining an Access Token
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* The client obtains an access token by authenticating with the
|
||||||
|
* authorization server and presenting its access grant (in the form of
|
||||||
|
* an authorization code, resource owner credentials, an assertion, or a
|
||||||
|
* refresh token).
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "authorization_code" grant types (for token obtaining).
|
||||||
|
*/
|
||||||
|
define("OAUTH2_GRANT_TYPE_AUTH_CODE", "authorization_code");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "password" grant types (for token obtaining).
|
||||||
|
*/
|
||||||
|
define("OAUTH2_GRANT_TYPE_USER_CREDENTIALS", "password");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "assertion" grant types (for token obtaining).
|
||||||
|
*/
|
||||||
|
define("OAUTH2_GRANT_TYPE_ASSERTION", "assertion");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "refresh_token" grant types (for token obtaining).
|
||||||
|
*/
|
||||||
|
define("OAUTH2_GRANT_TYPE_REFRESH_TOKEN", "refresh_token");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes "none" grant types (for token obtaining).
|
||||||
|
*/
|
||||||
|
define("OAUTH2_GRANT_TYPE_NONE", "none");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to filter out the grant type.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_GRANT_TYPE_REGEXP", "/^(authorization_code|password|assertion|refresh_token|none)$/");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup oauth2_section_5 Accessing a Protected Resource
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* Clients access protected resources by presenting an access token to
|
||||||
|
* the resource server. Access tokens act as bearer tokens, where the
|
||||||
|
* token string acts as a shared symmetric secret. This requires
|
||||||
|
* treating the access token with the same care as other secrets (e.g.
|
||||||
|
* end-user passwords). Access tokens SHOULD NOT be sent in the clear
|
||||||
|
* over an insecure channel.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to define the name of the OAuth access token parameter (POST/GET/etc.).
|
||||||
|
*
|
||||||
|
* IETF Draft sections 5.1.2 and 5.1.3 specify that it should be called
|
||||||
|
* "oauth_token" but other implementations use things like "access_token".
|
||||||
|
*
|
||||||
|
* I won't be heartbroken if you change it, but it might be better to adhere
|
||||||
|
* to the spec.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.2
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.3
|
||||||
|
*/
|
||||||
|
define("OAUTH2_TOKEN_PARAM_NAME", "oauth_token");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup oauth2_http_status HTTP status code
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Found" HTTP status code.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
|
||||||
|
*/
|
||||||
|
define("OAUTH2_HTTP_FOUND", "302 Found");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Bad Request" HTTP status code.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_HTTP_BAD_REQUEST", "400 Bad Request");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Unauthorized" HTTP status code.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_HTTP_UNAUTHORIZED", "401 Unauthorized");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Forbidden" HTTP status code.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_HTTP_FORBIDDEN", "403 Forbidden");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup oauth2_error Error handling
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @todo Extend for i18n.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request is missing a required parameter, includes an unsupported
|
||||||
|
* parameter or parameter value, or is otherwise malformed.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_INVALID_REQUEST", "invalid_request");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client identifier provided is invalid.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_INVALID_CLIENT", "invalid_client");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client is not authorized to use the requested response type.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_UNAUTHORIZED_CLIENT", "unauthorized_client");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The redirection URI provided does not match a pre-registered value.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_REDIRECT_URI_MISMATCH", "redirect_uri_mismatch");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end-user or authorization server denied the request.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_USER_DENIED", "access_denied");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The requested response type is not supported by the authorization server.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE", "unsupported_response_type");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The requested scope is invalid, unknown, or malformed.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_INVALID_SCOPE", "invalid_scope");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provided access grant is invalid, expired, or revoked (e.g. invalid
|
||||||
|
* assertion, expired authorization token, bad end-user password credentials,
|
||||||
|
* or mismatching authorization code and redirection URI).
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_INVALID_GRANT", "invalid_grant");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The access grant included - its type or another attribute - is not
|
||||||
|
* supported by the authorization server.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE", "unsupported_grant_type");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The access token provided is invalid. Resource servers SHOULD use this
|
||||||
|
* error code when receiving an expired token which cannot be refreshed to
|
||||||
|
* indicate to the client that a new authorization is necessary. The resource
|
||||||
|
* server MUST respond with the HTTP 401 (Unauthorized) status code.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_INVALID_TOKEN", "invalid_token");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The access token provided has expired. Resource servers SHOULD only use
|
||||||
|
* this error code when the client is expected to be able to handle the
|
||||||
|
* response and request a new access token using the refresh token issued
|
||||||
|
* with the expired access token. The resource server MUST respond with the
|
||||||
|
* HTTP 401 (Unauthorized) status code.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_EXPIRED_TOKEN", "expired_token");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request requires higher privileges than provided by the access token.
|
||||||
|
* The resource server SHOULD respond with the HTTP 403 (Forbidden) status
|
||||||
|
* code and MAY include the "scope" attribute with the scope necessary to
|
||||||
|
* access the protected resource.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
|
||||||
|
*/
|
||||||
|
define("OAUTH2_ERROR_INSUFFICIENT_SCOPE", "insufficient_scope");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth2.0 draft v10 server-side implementation.
|
||||||
|
*
|
||||||
|
* @author Originally written by Tim Ridgely <tim.ridgely@gmail.com>.
|
||||||
|
* @author Updated to draft v10 by Aaron Parecki <aaron@parecki.com>.
|
||||||
|
* @author Debug, coding style clean up and documented by Edison Wong <hswong3i@pantarei-design.com>.
|
||||||
|
*/
|
||||||
|
abstract class OAuth2 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of persistent variables stored.
|
||||||
|
*/
|
||||||
|
protected $conf = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a persistent variable.
|
||||||
|
*
|
||||||
|
* To avoid problems, always use lower case for persistent variable names.
|
||||||
|
*
|
||||||
|
* @param $name
|
||||||
|
* The name of the variable to return.
|
||||||
|
* @param $default
|
||||||
|
* The default value to use if this variable has never been set.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The value of the variable.
|
||||||
|
*/
|
||||||
|
public function getVariable($name, $default = NULL) {
|
||||||
|
return isset($this->conf[$name]) ? $this->conf[$name] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a persistent variable.
|
||||||
|
*
|
||||||
|
* To avoid problems, always use lower case for persistent variable names.
|
||||||
|
*
|
||||||
|
* @param $name
|
||||||
|
* The name of the variable to set.
|
||||||
|
* @param $value
|
||||||
|
* The value to set.
|
||||||
|
*/
|
||||||
|
public function setVariable($name, $value) {
|
||||||
|
$this->conf[$name] = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses must implement the following functions.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that the client credentials is valid.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be check with.
|
||||||
|
* @param $client_secret
|
||||||
|
* (optional) If a secret is required, check that they've given the right one.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* TRUE if client credentials are valid, and MUST return FALSE if invalid.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2.1
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_2
|
||||||
|
*/
|
||||||
|
abstract protected function checkClientCredentials($client_id, $client_secret = NULL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the registered redirect URI of corresponding client_id.
|
||||||
|
*
|
||||||
|
* OAuth says we should store request URIs for each registered client.
|
||||||
|
* Implement this function to grab the stored URI for a given client id.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be check with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Registered redirect URI of corresponding client identifier, and MUST
|
||||||
|
* return FALSE if the given client does not exist or is invalid.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
abstract protected function getRedirectUri($client_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the supplied oauth_token from storage.
|
||||||
|
*
|
||||||
|
* We need to retrieve access token data as we create and verify tokens.
|
||||||
|
*
|
||||||
|
* @param $oauth_token
|
||||||
|
* oauth_token to be check with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An associative array as below, and return NULL if the supplied oauth_token
|
||||||
|
* is invalid:
|
||||||
|
* - client_id: Stored client identifier.
|
||||||
|
* - expires: Stored expiration in unix timestamp.
|
||||||
|
* - scope: (optional) Stored scope values in space-separated string.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_5
|
||||||
|
*/
|
||||||
|
abstract protected function getAccessToken($oauth_token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the supplied access token values to storage.
|
||||||
|
*
|
||||||
|
* We need to store access token data as we create and verify tokens.
|
||||||
|
*
|
||||||
|
* @param $oauth_token
|
||||||
|
* oauth_token to be stored.
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be stored.
|
||||||
|
* @param $expires
|
||||||
|
* Expiration to be stored.
|
||||||
|
* @param $scope
|
||||||
|
* (optional) Scopes to be stored in space-separated string.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
abstract protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL);
|
||||||
|
|
||||||
|
// Stuff that should get overridden by subclasses.
|
||||||
|
//
|
||||||
|
// I don't want to make these abstract, because then subclasses would have
|
||||||
|
// to implement all of them, which is too much work.
|
||||||
|
//
|
||||||
|
// So they're just stubs. Override the ones you need.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return supported grant types.
|
||||||
|
*
|
||||||
|
* You should override this function with something, or else your OAuth
|
||||||
|
* provider won't support any grant types!
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list as below. If you support all grant types, then you'd do:
|
||||||
|
* @code
|
||||||
|
* return array(
|
||||||
|
* OAUTH2_GRANT_TYPE_AUTH_CODE,
|
||||||
|
* OAUTH2_GRANT_TYPE_USER_CREDENTIALS,
|
||||||
|
* OAUTH2_GRANT_TYPE_ASSERTION,
|
||||||
|
* OAUTH2_GRANT_TYPE_REFRESH_TOKEN,
|
||||||
|
* OAUTH2_GRANT_TYPE_NONE,
|
||||||
|
* );
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function getSupportedGrantTypes() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return supported authorization response types.
|
||||||
|
*
|
||||||
|
* You should override this function with your supported response types.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list as below. If you support all authorization response types,
|
||||||
|
* then you'd do:
|
||||||
|
* @code
|
||||||
|
* return array(
|
||||||
|
* OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
|
||||||
|
* OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
|
||||||
|
* OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN,
|
||||||
|
* );
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
protected function getSupportedAuthResponseTypes() {
|
||||||
|
return array(
|
||||||
|
OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
|
||||||
|
OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
|
||||||
|
OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return supported scopes.
|
||||||
|
*
|
||||||
|
* If you want to support scope use, then have this function return a list
|
||||||
|
* of all acceptable scopes (used to throw the invalid-scope error).
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list as below, for example:
|
||||||
|
* @code
|
||||||
|
* return array(
|
||||||
|
* 'my-friends',
|
||||||
|
* 'photos',
|
||||||
|
* 'whatever-else',
|
||||||
|
* );
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
protected function getSupportedScopes() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check restricted authorization response types of corresponding Client
|
||||||
|
* identifier.
|
||||||
|
*
|
||||||
|
* If you want to restrict clients to certain authorization response types,
|
||||||
|
* override this function.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be check with.
|
||||||
|
* @param $response_type
|
||||||
|
* Authorization response type to be check with, would be one of the
|
||||||
|
* values contained in OAUTH2_AUTH_RESPONSE_TYPE_REGEXP.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* TRUE if the authorization response type is supported by this
|
||||||
|
* client identifier, and FALSE if it isn't.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
protected function checkRestrictedAuthResponseType($client_id, $response_type) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check restricted grant types of corresponding client identifier.
|
||||||
|
*
|
||||||
|
* If you want to restrict clients to certain grant types, override this
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be check with.
|
||||||
|
* @param $grant_type
|
||||||
|
* Grant type to be check with, would be one of the values contained in
|
||||||
|
* OAUTH2_GRANT_TYPE_REGEXP.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* TRUE if the grant type is supported by this client identifier, and
|
||||||
|
* FALSE if it isn't.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function checkRestrictedGrantType($client_id, $grant_type) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions that help grant access tokens for various grant types.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch authorization code data (probably the most common grant type).
|
||||||
|
*
|
||||||
|
* Retrieve the stored data for the given authorization code.
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_AUTH_CODE.
|
||||||
|
*
|
||||||
|
* @param $code
|
||||||
|
* Authorization code to be check with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An associative array as below, and NULL if the code is invalid:
|
||||||
|
* - client_id: Stored client identifier.
|
||||||
|
* - redirect_uri: Stored redirect URI.
|
||||||
|
* - expires: Stored expiration in unix timestamp.
|
||||||
|
* - scope: (optional) Stored scope values in space-separated string.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.1
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function getAuthCode($code) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the provided authorization code values and store them somewhere.
|
||||||
|
*
|
||||||
|
* This function should be the storage counterpart to getAuthCode().
|
||||||
|
*
|
||||||
|
* If storage fails for some reason, we're not currently checking for
|
||||||
|
* any sort of success/failure, so you should bail out of the script
|
||||||
|
* and provide a descriptive fail message.
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_AUTH_CODE.
|
||||||
|
*
|
||||||
|
* @param $code
|
||||||
|
* Authorization code to be stored.
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be stored.
|
||||||
|
* @param $redirect_uri
|
||||||
|
* Redirect URI to be stored.
|
||||||
|
* @param $expires
|
||||||
|
* Expiration to be stored.
|
||||||
|
* @param $scope
|
||||||
|
* (optional) Scopes to be stored in space-separated string.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant access tokens for basic user credentials.
|
||||||
|
*
|
||||||
|
* Check the supplied username and password for validity.
|
||||||
|
*
|
||||||
|
* You can also use the $client_id param to do any checks required based
|
||||||
|
* on a client, if you need that.
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_USER_CREDENTIALS.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be check with.
|
||||||
|
* @param $username
|
||||||
|
* Username to be check with.
|
||||||
|
* @param $password
|
||||||
|
* Password to be check with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* TRUE if the username and password are valid, and FALSE if it isn't.
|
||||||
|
* Moreover, if the username and password are valid, and you want to
|
||||||
|
* verify the scope of a user's access, return an associative array
|
||||||
|
* with the scope values as below. We'll check the scope you provide
|
||||||
|
* against the requested scope before providing an access token:
|
||||||
|
* @code
|
||||||
|
* return array(
|
||||||
|
* 'scope' => <stored scope values (space-separated string)>,
|
||||||
|
* );
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.2
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function checkUserCredentials($client_id, $username, $password) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant access tokens for assertions.
|
||||||
|
*
|
||||||
|
* Check the supplied assertion for validity.
|
||||||
|
*
|
||||||
|
* You can also use the $client_id param to do any checks required based
|
||||||
|
* on a client, if you need that.
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_ASSERTION.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be check with.
|
||||||
|
* @param $assertion_type
|
||||||
|
* The format of the assertion as defined by the authorization server.
|
||||||
|
* @param $assertion
|
||||||
|
* The assertion.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* TRUE if the assertion is valid, and FALSE if it isn't. Moreover, if
|
||||||
|
* the assertion is valid, and you want to verify the scope of an access
|
||||||
|
* request, return an associative array with the scope values as below.
|
||||||
|
* We'll check the scope you provide against the requested scope before
|
||||||
|
* providing an access token:
|
||||||
|
* @code
|
||||||
|
* return array(
|
||||||
|
* 'scope' => <stored scope values (space-separated string)>,
|
||||||
|
* );
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function checkAssertion($client_id, $assertion_type, $assertion) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant refresh access tokens.
|
||||||
|
*
|
||||||
|
* Retrieve the stored data for the given refresh token.
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN.
|
||||||
|
*
|
||||||
|
* @param $refresh_token
|
||||||
|
* Refresh token to be check with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An associative array as below, and NULL if the refresh_token is
|
||||||
|
* invalid:
|
||||||
|
* - client_id: Stored client identifier.
|
||||||
|
* - expires: Stored expiration unix timestamp.
|
||||||
|
* - scope: (optional) Stored scope values in space-separated string.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.4
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function getRefreshToken($refresh_token) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the provided refresh token values and store them somewhere.
|
||||||
|
*
|
||||||
|
* This function should be the storage counterpart to getRefreshToken().
|
||||||
|
*
|
||||||
|
* If storage fails for some reason, we're not currently checking for
|
||||||
|
* any sort of success/failure, so you should bail out of the script
|
||||||
|
* and provide a descriptive fail message.
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN.
|
||||||
|
*
|
||||||
|
* @param $refresh_token
|
||||||
|
* Refresh token to be stored.
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier to be stored.
|
||||||
|
* @param $expires
|
||||||
|
* expires to be stored.
|
||||||
|
* @param $scope
|
||||||
|
* (optional) Scopes to be stored in space-separated string.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function setRefreshToken($refresh_token, $client_id, $expires, $scope = NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expire a used refresh token.
|
||||||
|
*
|
||||||
|
* This is not explicitly required in the spec, but is almost implied.
|
||||||
|
* After granting a new refresh token, the old one is no longer useful and
|
||||||
|
* so should be forcibly expired in the data store so it can't be used again.
|
||||||
|
*
|
||||||
|
* If storage fails for some reason, we're not currently checking for
|
||||||
|
* any sort of success/failure, so you should bail out of the script
|
||||||
|
* and provide a descriptive fail message.
|
||||||
|
*
|
||||||
|
* @param $refresh_token
|
||||||
|
* Refresh token to be expirse.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function unsetRefreshToken($refresh_token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant access tokens for the "none" grant type.
|
||||||
|
*
|
||||||
|
* Not really described in the IETF Draft, so I just left a method
|
||||||
|
* stub... Do whatever you want!
|
||||||
|
*
|
||||||
|
* Required for OAUTH2_GRANT_TYPE_NONE.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function checkNoneAccess($client_id) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default authentication realm for WWW-Authenticate header.
|
||||||
|
*
|
||||||
|
* Change this to whatever authentication realm you want to send in a
|
||||||
|
* WWW-Authenticate header.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A string that you want to send in a WWW-Authenticate header.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_error
|
||||||
|
*/
|
||||||
|
protected function getDefaultAuthenticationRealm() {
|
||||||
|
return "Service";
|
||||||
|
}
|
||||||
|
|
||||||
|
// End stuff that should get overridden.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an OAuth2.0 server-side instance.
|
||||||
|
*
|
||||||
|
* @param $config
|
||||||
|
* An associative array as below:
|
||||||
|
* - access_token_lifetime: (optional) The lifetime of access token in
|
||||||
|
* seconds.
|
||||||
|
* - auth_code_lifetime: (optional) The lifetime of authorization code in
|
||||||
|
* seconds.
|
||||||
|
* - refresh_token_lifetime: (optional) The lifetime of refresh token in
|
||||||
|
* seconds.
|
||||||
|
* - display_error: (optional) Whether to show verbose error messages in
|
||||||
|
* the response.
|
||||||
|
*/
|
||||||
|
public function __construct($config = array()) {
|
||||||
|
foreach ($config as $name => $value) {
|
||||||
|
$this->setVariable($name, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource protecting (Section 5).
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a valid access token has been provided.
|
||||||
|
*
|
||||||
|
* The scope parameter defines any required scope that the token must have.
|
||||||
|
* If a scope param is provided and the token does not have the required
|
||||||
|
* scope, we bounce the request.
|
||||||
|
*
|
||||||
|
* Some implementations may choose to return a subset of the protected
|
||||||
|
* resource (i.e. "public" data) if the user has not provided an access
|
||||||
|
* token or if the access token is invalid or expired.
|
||||||
|
*
|
||||||
|
* The IETF spec says that we should send a 401 Unauthorized header and
|
||||||
|
* bail immediately so that's what the defaults are set to.
|
||||||
|
*
|
||||||
|
* @param $scope
|
||||||
|
* A space-separated string of required scope(s), if you want to check
|
||||||
|
* for scope.
|
||||||
|
* @param $exit_not_present
|
||||||
|
* If TRUE and no access token is provided, send a 401 header and exit,
|
||||||
|
* otherwise return FALSE.
|
||||||
|
* @param $exit_invalid
|
||||||
|
* If TRUE and the implementation of getAccessToken() returns NULL, exit,
|
||||||
|
* otherwise return FALSE.
|
||||||
|
* @param $exit_expired
|
||||||
|
* If TRUE and the access token has expired, exit, otherwise return FALSE.
|
||||||
|
* @param $exit_scope
|
||||||
|
* If TRUE the access token does not have the required scope(s), exit,
|
||||||
|
* otherwise return FALSE.
|
||||||
|
* @param $realm
|
||||||
|
* If you want to specify a particular realm for the WWW-Authenticate
|
||||||
|
* header, supply it here.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_5
|
||||||
|
*/
|
||||||
|
public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) {
|
||||||
|
$token_param = $this->getAccessTokenParams();
|
||||||
|
if ($token_param === FALSE) // Access token was not provided
|
||||||
|
return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE;
|
||||||
|
// Get the stored token data (from the implementing subclass)
|
||||||
|
$token = $this->getAccessToken($token_param);
|
||||||
|
if ($token === NULL)
|
||||||
|
return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE;
|
||||||
|
|
||||||
|
// Check token expiration (I'm leaving this check separated, later we'll fill in better error messages)
|
||||||
|
if (isset($token["expires"]) && time() > $token["expires"])
|
||||||
|
return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE;
|
||||||
|
|
||||||
|
// Check scope, if provided
|
||||||
|
// If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error
|
||||||
|
if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"])))
|
||||||
|
return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if everything in required scope is contained in available scope.
|
||||||
|
*
|
||||||
|
* @param $required_scope
|
||||||
|
* Required scope to be check with.
|
||||||
|
* @param $available_scope
|
||||||
|
* Available scope to be compare with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* TRUE if everything in required scope is contained in available scope,
|
||||||
|
* and False if it isn't.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_5
|
||||||
|
*/
|
||||||
|
private function checkScope($required_scope, $available_scope) {
|
||||||
|
// The required scope should match or be a subset of the available scope
|
||||||
|
if (!is_array($required_scope))
|
||||||
|
$required_scope = explode(" ", $required_scope);
|
||||||
|
|
||||||
|
if (!is_array($available_scope))
|
||||||
|
$available_scope = explode(" ", $available_scope);
|
||||||
|
|
||||||
|
return (count(array_diff($required_scope, $available_scope)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulls the access token out of the HTTP request.
|
||||||
|
*
|
||||||
|
* Either from the Authorization header or GET/POST/etc.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Access token value if present, and FALSE if it isn't.
|
||||||
|
*
|
||||||
|
* @todo Support PUT or DELETE.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_5
|
||||||
|
*/
|
||||||
|
private function getAccessTokenParams() {
|
||||||
|
$auth_header = $this->getAuthorizationHeader();
|
||||||
|
|
||||||
|
if ($auth_header !== FALSE) {
|
||||||
|
// Make sure only the auth header is set
|
||||||
|
if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME]) || isset($_POST[OAUTH2_TOKEN_PARAM_NAME]))
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth token found in GET or POST when token present in header');
|
||||||
|
|
||||||
|
$auth_header = trim($auth_header);
|
||||||
|
|
||||||
|
// Make sure it's Token authorization
|
||||||
|
if (strcmp(substr($auth_header, 0, 5), "OAuth ") !== 0)
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth header found that doesn\'t start with "OAuth"');
|
||||||
|
|
||||||
|
// Parse the rest of the header
|
||||||
|
if (preg_match('/\s*OAuth\s*="(.+)"/', substr($auth_header, 5), $matches) == 0 || count($matches) < 2)
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Malformed auth header');
|
||||||
|
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME])) {
|
||||||
|
if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) // Both GET and POST are not allowed
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Only send the token in GET or POST, not both');
|
||||||
|
|
||||||
|
return $_GET[OAUTH2_TOKEN_PARAM_NAME];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME]))
|
||||||
|
return $_POST[OAUTH2_TOKEN_PARAM_NAME];
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access token granting (Section 4).
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant or deny a requested access token.
|
||||||
|
*
|
||||||
|
* This would be called from the "/token" endpoint as defined in the spec.
|
||||||
|
* Obviously, you can call your endpoint whatever you want.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
public function grantAccessToken() {
|
||||||
|
$filters = array(
|
||||||
|
"grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"scope" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"code" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
|
||||||
|
"username" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"password" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"assertion_type" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"assertion" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
);
|
||||||
|
|
||||||
|
$input = filter_input_array(INPUT_POST, $filters);
|
||||||
|
|
||||||
|
// Grant Type must be specified.
|
||||||
|
if (!$input["grant_type"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
|
||||||
|
|
||||||
|
// Make sure we've implemented the requested grant type
|
||||||
|
if (!in_array($input["grant_type"], $this->getSupportedGrantTypes()))
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE);
|
||||||
|
|
||||||
|
// Authorize the client
|
||||||
|
$client = $this->getClientCredentials();
|
||||||
|
|
||||||
|
if ($this->checkClientCredentials($client[0], $client[1]) === FALSE)
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
|
||||||
|
|
||||||
|
if (!$this->checkRestrictedGrantType($client[0], $input["grant_type"]))
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT);
|
||||||
|
|
||||||
|
// Do the granting
|
||||||
|
switch ($input["grant_type"]) {
|
||||||
|
case OAUTH2_GRANT_TYPE_AUTH_CODE:
|
||||||
|
if (!$input["code"] || !$input["redirect_uri"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
|
||||||
|
|
||||||
|
$stored = $this->getAuthCode($input["code"]);
|
||||||
|
|
||||||
|
// Ensure that the input uri starts with the stored uri
|
||||||
|
if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
|
||||||
|
|
||||||
|
if ($stored["expires"] < time())
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case OAUTH2_GRANT_TYPE_USER_CREDENTIALS:
|
||||||
|
if (!$input["username"] || !$input["password"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required');
|
||||||
|
|
||||||
|
$stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]);
|
||||||
|
|
||||||
|
if ($stored === FALSE)
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case OAUTH2_GRANT_TYPE_ASSERTION:
|
||||||
|
if (!$input["assertion_type"] || !$input["assertion"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
|
||||||
|
|
||||||
|
$stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]);
|
||||||
|
|
||||||
|
if ($stored === FALSE)
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case OAUTH2_GRANT_TYPE_REFRESH_TOKEN:
|
||||||
|
if (!$input["refresh_token"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found');
|
||||||
|
|
||||||
|
$stored = $this->getRefreshToken($input["refresh_token"]);
|
||||||
|
|
||||||
|
if ($stored === NULL || $client[0] != $stored["client_id"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
|
||||||
|
|
||||||
|
if ($stored["expires"] < time())
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);
|
||||||
|
|
||||||
|
// store the refresh token locally so we can delete it when a new refresh token is generated
|
||||||
|
$this->setVariable('_old_refresh_token', $stored["token"]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case OAUTH2_GRANT_TYPE_NONE:
|
||||||
|
$stored = $this->checkNoneAccess($client[0]);
|
||||||
|
|
||||||
|
if ($stored === FALSE)
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check scope, if provided
|
||||||
|
if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"])))
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE);
|
||||||
|
|
||||||
|
if (!$input["scope"])
|
||||||
|
$input["scope"] = NULL;
|
||||||
|
|
||||||
|
$token = $this->createAccessToken($client[0], $input["scope"]);
|
||||||
|
|
||||||
|
$this->sendJsonHeaders();
|
||||||
|
echo json_encode($token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function used to get the client credentials from HTTP basic
|
||||||
|
* auth or POST data.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list containing the client identifier and password, for example
|
||||||
|
* @code
|
||||||
|
* return array(
|
||||||
|
* $_POST["client_id"],
|
||||||
|
* $_POST["client_secret"],
|
||||||
|
* );
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_2
|
||||||
|
*/
|
||||||
|
protected function getClientCredentials() {
|
||||||
|
if (isset($_SERVER["PHP_AUTH_USER"]) && $_POST && isset($_POST["client_id"]))
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
|
||||||
|
|
||||||
|
// Try basic auth
|
||||||
|
if (isset($_SERVER["PHP_AUTH_USER"]))
|
||||||
|
return array($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]);
|
||||||
|
|
||||||
|
// Try POST
|
||||||
|
if ($_POST && isset($_POST["client_id"])) {
|
||||||
|
if (isset($_POST["client_secret"]))
|
||||||
|
return array($_POST["client_id"], $_POST["client_secret"]);
|
||||||
|
|
||||||
|
return array($_POST["client_id"], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No credentials were specified
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End-user/client Authorization (Section 3 of IETF Draft).
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull the authorization request data out of the HTTP request.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The authorization parameters so the authorization server can prompt
|
||||||
|
* the user for approval if valid.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
public function getAuthorizeParams() {
|
||||||
|
$filters = array(
|
||||||
|
"client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"response_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_AUTH_RESPONSE_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
|
||||||
|
"state" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
"scope" => array("flags" => FILTER_REQUIRE_SCALAR),
|
||||||
|
);
|
||||||
|
|
||||||
|
$input = filter_input_array(INPUT_GET, $filters);
|
||||||
|
|
||||||
|
// Make sure a valid client id was supplied
|
||||||
|
if (!$input["client_id"]) {
|
||||||
|
if ($input["redirect_uri"])
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]);
|
||||||
|
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); // We don't have a good URI to use
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect_uri is not required if already established via other channels
|
||||||
|
// check an existing redirect URI against the one supplied
|
||||||
|
$redirect_uri = $this->getRedirectUri($input["client_id"]);
|
||||||
|
|
||||||
|
// At least one of: existing redirect URI or input redirect URI must be specified
|
||||||
|
if (!$redirect_uri && !$input["redirect_uri"])
|
||||||
|
$this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST);
|
||||||
|
|
||||||
|
// getRedirectUri() should return FALSE if the given client ID is invalid
|
||||||
|
// this probably saves us from making a separate db call, and simplifies the method set
|
||||||
|
if ($redirect_uri === FALSE)
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]);
|
||||||
|
|
||||||
|
// If there's an existing uri and one from input, verify that they match
|
||||||
|
if ($redirect_uri && $input["redirect_uri"]) {
|
||||||
|
// Ensure that the input uri starts with the stored uri
|
||||||
|
if (strcasecmp(substr($input["redirect_uri"], 0, strlen($redirect_uri)), $redirect_uri) !== 0)
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]);
|
||||||
|
}
|
||||||
|
elseif ($redirect_uri) { // They did not provide a uri from input, so use the stored one
|
||||||
|
$input["redirect_uri"] = $redirect_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// type and client_id are required
|
||||||
|
if (!$input["response_type"])
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]);
|
||||||
|
|
||||||
|
// Check requested auth response type against the list of supported types
|
||||||
|
if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE)
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]);
|
||||||
|
|
||||||
|
// Restrict clients to certain authorization response types
|
||||||
|
if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE)
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]);
|
||||||
|
|
||||||
|
// Validate that the requested scope is supported
|
||||||
|
if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes()))
|
||||||
|
$this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]);
|
||||||
|
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the user appropriately after approval.
|
||||||
|
*
|
||||||
|
* After the user has approved or denied the access request the
|
||||||
|
* authorization server should call this function to redirect the user
|
||||||
|
* appropriately.
|
||||||
|
*
|
||||||
|
* @param $is_authorized
|
||||||
|
* TRUE or FALSE depending on whether the user authorized the access.
|
||||||
|
* @param $params
|
||||||
|
* An associative array as below:
|
||||||
|
* - response_type: The requested response: an access token, an
|
||||||
|
* authorization code, or both.
|
||||||
|
* - client_id: The client identifier as described in Section 2.
|
||||||
|
* - redirect_uri: An absolute URI to which the authorization server
|
||||||
|
* will redirect the user-agent to when the end-user authorization
|
||||||
|
* step is completed.
|
||||||
|
* - scope: (optional) The scope of the access request expressed as a
|
||||||
|
* list of space-delimited strings.
|
||||||
|
* - state: (optional) An opaque value used by the client to maintain
|
||||||
|
* state between the request and callback.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
public function finishClientAuthorization($is_authorized, $params = array()) {
|
||||||
|
$params += array(
|
||||||
|
'scope' => NULL,
|
||||||
|
'state' => NULL,
|
||||||
|
);
|
||||||
|
extract($params);
|
||||||
|
|
||||||
|
if ($state !== NULL)
|
||||||
|
$result["query"]["state"] = $state;
|
||||||
|
|
||||||
|
if ($is_authorized === FALSE) {
|
||||||
|
$result["query"]["error"] = OAUTH2_ERROR_USER_DENIED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN)
|
||||||
|
$result["query"]["code"] = $this->createAuthCode($client_id, $redirect_uri, $scope);
|
||||||
|
|
||||||
|
if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN)
|
||||||
|
$result["fragment"] = $this->createAccessToken($client_id, $scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->doRedirectUriCallback($redirect_uri, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other/utility functions.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the user agent.
|
||||||
|
*
|
||||||
|
* Handle both redirect for success or error response.
|
||||||
|
*
|
||||||
|
* @param $redirect_uri
|
||||||
|
* An absolute URI to which the authorization server will redirect
|
||||||
|
* the user-agent to when the end-user authorization step is completed.
|
||||||
|
* @param $params
|
||||||
|
* Parameters to be pass though buildUri().
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
private function doRedirectUriCallback($redirect_uri, $params) {
|
||||||
|
header("HTTP/1.1 ". OAUTH2_HTTP_FOUND);
|
||||||
|
header("Location: " . $this->buildUri($redirect_uri, $params));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the absolute URI based on supplied URI and parameters.
|
||||||
|
*
|
||||||
|
* @param $uri
|
||||||
|
* An absolute URI.
|
||||||
|
* @param $params
|
||||||
|
* Parameters to be append as GET.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An absolute URI with supplied parameters.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
private function buildUri($uri, $params) {
|
||||||
|
$parse_url = parse_url($uri);
|
||||||
|
|
||||||
|
// Add our params to the parsed uri
|
||||||
|
foreach ($params as $k => $v) {
|
||||||
|
if (isset($parse_url[$k]))
|
||||||
|
$parse_url[$k] .= "&" . http_build_query($v);
|
||||||
|
else
|
||||||
|
$parse_url[$k] = http_build_query($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put humpty dumpty back together
|
||||||
|
return
|
||||||
|
((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
|
||||||
|
. ((isset($parse_url["user"])) ? $parse_url["user"] . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
|
||||||
|
. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
|
||||||
|
. ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
|
||||||
|
. ((isset($parse_url["path"])) ? $parse_url["path"] : "")
|
||||||
|
. ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "")
|
||||||
|
. ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the creation of access token, also issue refresh token if support.
|
||||||
|
*
|
||||||
|
* This belongs in a separate factory, but to keep it simple, I'm just
|
||||||
|
* keeping it here.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier related to the access token.
|
||||||
|
* @param $scope
|
||||||
|
* (optional) Scopes to be stored in space-separated string.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function createAccessToken($client_id, $scope = NULL) {
|
||||||
|
$token = array(
|
||||||
|
"access_token" => $this->genAccessToken(),
|
||||||
|
"expires_in" => $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME),
|
||||||
|
"scope" => $scope
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->setAccessToken($token["access_token"], $client_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope);
|
||||||
|
|
||||||
|
// Issue a refresh token also, if we support them
|
||||||
|
if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) {
|
||||||
|
$token["refresh_token"] = $this->genAccessToken();
|
||||||
|
$this->setRefreshToken($token["refresh_token"], $client_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope);
|
||||||
|
// If we've granted a new refresh token, expire the old one
|
||||||
|
if ($this->getVariable('_old_refresh_token'))
|
||||||
|
$this->unsetRefreshToken($this->getVariable('_old_refresh_token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the creation of auth code.
|
||||||
|
*
|
||||||
|
* This belongs in a separate factory, but to keep it simple, I'm just
|
||||||
|
* keeping it here.
|
||||||
|
*
|
||||||
|
* @param $client_id
|
||||||
|
* Client identifier related to the access token.
|
||||||
|
* @param $redirect_uri
|
||||||
|
* An absolute URI to which the authorization server will redirect the
|
||||||
|
* user-agent to when the end-user authorization step is completed.
|
||||||
|
* @param $scope
|
||||||
|
* (optional) Scopes to be stored in space-separated string.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
private function createAuthCode($client_id, $redirect_uri, $scope = NULL) {
|
||||||
|
$code = $this->genAuthCode();
|
||||||
|
$this->setAuthCode($code, $client_id, $redirect_uri, time() + $this->getVariable('auth_code_lifetime', OAUTH2_DEFAULT_AUTH_CODE_LIFETIME), $scope);
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate unique access token.
|
||||||
|
*
|
||||||
|
* Implementing classes may want to override these function to implement
|
||||||
|
* other access token or auth code generation schemes.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An unique access token.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
protected function genAccessToken() {
|
||||||
|
return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate unique auth code.
|
||||||
|
*
|
||||||
|
* Implementing classes may want to override these function to implement
|
||||||
|
* other access token or auth code generation schemes.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An unique auth code.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_3
|
||||||
|
*/
|
||||||
|
protected function genAuthCode() {
|
||||||
|
return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull out the Authorization HTTP header and return it.
|
||||||
|
*
|
||||||
|
* Implementing classes may need to override this function for use on
|
||||||
|
* non-Apache web servers.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The Authorization HTTP header, and FALSE if does not exist.
|
||||||
|
*
|
||||||
|
* @todo Handle Authorization HTTP header for non-Apache web servers.
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_5
|
||||||
|
*/
|
||||||
|
private function getAuthorizationHeader() {
|
||||||
|
if (array_key_exists("HTTP_AUTHORIZATION", $_SERVER))
|
||||||
|
return $_SERVER["HTTP_AUTHORIZATION"];
|
||||||
|
|
||||||
|
if (function_exists("apache_request_headers")) {
|
||||||
|
$headers = apache_request_headers();
|
||||||
|
|
||||||
|
if (array_key_exists("Authorization", $headers))
|
||||||
|
return $headers["Authorization"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send out HTTP headers for JSON.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.2
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_section_4
|
||||||
|
*/
|
||||||
|
private function sendJsonHeaders() {
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
header("Cache-Control: no-store");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the end-user's user agent with error message.
|
||||||
|
*
|
||||||
|
* @param $redirect_uri
|
||||||
|
* An absolute URI to which the authorization server will redirect the
|
||||||
|
* user-agent to when the end-user authorization step is completed.
|
||||||
|
* @param $error
|
||||||
|
* A single error code as described in Section 3.2.1.
|
||||||
|
* @param $error_description
|
||||||
|
* (optional) A human-readable text providing additional information,
|
||||||
|
* used to assist in the understanding and resolution of the error
|
||||||
|
* occurred.
|
||||||
|
* @param $error_uri
|
||||||
|
* (optional) A URI identifying a human-readable web page with
|
||||||
|
* information about the error, used to provide the end-user with
|
||||||
|
* additional information about the error.
|
||||||
|
* @param $state
|
||||||
|
* (optional) REQUIRED if the "state" parameter was present in the client
|
||||||
|
* authorization request. Set to the exact value received from the client.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_error
|
||||||
|
*/
|
||||||
|
private function errorDoRedirectUriCallback($redirect_uri, $error, $error_description = NULL, $error_uri = NULL, $state = NULL) {
|
||||||
|
$result["query"]["error"] = $error;
|
||||||
|
|
||||||
|
if ($state)
|
||||||
|
$result["query"]["state"] = $state;
|
||||||
|
|
||||||
|
if ($this->getVariable('display_error') && $error_description)
|
||||||
|
$result["query"]["error_description"] = $error_description;
|
||||||
|
|
||||||
|
if ($this->getVariable('display_error') && $error_uri)
|
||||||
|
$result["query"]["error_uri"] = $error_uri;
|
||||||
|
|
||||||
|
$this->doRedirectUriCallback($redirect_uri, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send out error message in JSON.
|
||||||
|
*
|
||||||
|
* @param $http_status_code
|
||||||
|
* HTTP status code message as predefined.
|
||||||
|
* @param $error
|
||||||
|
* A single error code.
|
||||||
|
* @param $error_description
|
||||||
|
* (optional) A human-readable text providing additional information,
|
||||||
|
* used to assist in the understanding and resolution of the error
|
||||||
|
* occurred.
|
||||||
|
* @param $error_uri
|
||||||
|
* (optional) A URI identifying a human-readable web page with
|
||||||
|
* information about the error, used to provide the end-user with
|
||||||
|
* additional information about the error.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_error
|
||||||
|
*/
|
||||||
|
private function errorJsonResponse($http_status_code, $error, $error_description = NULL, $error_uri = NULL) {
|
||||||
|
$result['error'] = $error;
|
||||||
|
|
||||||
|
if ($this->getVariable('display_error') && $error_description)
|
||||||
|
$result["error_description"] = $error_description;
|
||||||
|
|
||||||
|
if ($this->getVariable('display_error') && $error_uri)
|
||||||
|
$result["error_uri"] = $error_uri;
|
||||||
|
|
||||||
|
header("HTTP/1.1 " . $http_status_code);
|
||||||
|
$this->sendJsonHeaders();
|
||||||
|
echo json_encode($result);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a 401 unauthorized header with the given realm and an error, if
|
||||||
|
* provided.
|
||||||
|
*
|
||||||
|
* @param $http_status_code
|
||||||
|
* HTTP status code message as predefined.
|
||||||
|
* @param $realm
|
||||||
|
* The "realm" attribute is used to provide the protected resources
|
||||||
|
* partition as defined by [RFC2617].
|
||||||
|
* @param $scope
|
||||||
|
* A space-delimited list of scope values indicating the required scope
|
||||||
|
* of the access token for accessing the requested resource.
|
||||||
|
* @param $error
|
||||||
|
* The "error" attribute is used to provide the client with the reason
|
||||||
|
* why the access request was declined.
|
||||||
|
* @param $error_description
|
||||||
|
* (optional) The "error_description" attribute provides a human-readable text
|
||||||
|
* containing additional information, used to assist in the understanding
|
||||||
|
* and resolution of the error occurred.
|
||||||
|
* @param $error_uri
|
||||||
|
* (optional) The "error_uri" attribute provides a URI identifying a human-readable
|
||||||
|
* web page with information about the error, used to offer the end-user
|
||||||
|
* with additional information about the error. If the value is not an
|
||||||
|
* absolute URI, it is relative to the URI of the requested protected
|
||||||
|
* resource.
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2
|
||||||
|
*
|
||||||
|
* @ingroup oauth2_error
|
||||||
|
*/
|
||||||
|
private function errorWWWAuthenticateResponseHeader($http_status_code, $realm, $error, $error_description = NULL, $error_uri = NULL, $scope = NULL) {
|
||||||
|
$realm = $realm === NULL ? $this->getDefaultAuthenticationRealm() : $realm;
|
||||||
|
|
||||||
|
$result = "WWW-Authenticate: OAuth realm='" . $realm . "'";
|
||||||
|
|
||||||
|
if ($error)
|
||||||
|
$result .= ", error='" . $error . "'";
|
||||||
|
|
||||||
|
if ($this->getVariable('display_error') && $error_description)
|
||||||
|
$result .= ", error_description='" . $error_description . "'";
|
||||||
|
|
||||||
|
if ($this->getVariable('display_error') && $error_uri)
|
||||||
|
$result .= ", error_uri='" . $error_uri . "'";
|
||||||
|
|
||||||
|
if ($scope)
|
||||||
|
$result .= ", scope='" . $scope . "'";
|
||||||
|
|
||||||
|
header("HTTP/1.1 ". $http_status_code);
|
||||||
|
header($result);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
721
library/oauth2-php/lib/OAuth2Client.inc
Normal file
721
library/oauth2-php/lib/OAuth2Client.inc
Normal file
|
@ -0,0 +1,721 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default Cache Lifetime (in seconds).
|
||||||
|
*/
|
||||||
|
define("OAUTH2_DEFAULT_EXPIRES_IN", 3600);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default Base domain for the Cookie.
|
||||||
|
*/
|
||||||
|
define("OAUTH2_DEFAULT_BASE_DOMAIN", '');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth2.0 draft v10 client-side implementation.
|
||||||
|
*
|
||||||
|
* @author Originally written by Naitik Shah <naitik@facebook.com>.
|
||||||
|
* @author Update to draft v10 by Edison Wong <hswong3i@pantarei-design.com>.
|
||||||
|
*
|
||||||
|
* @sa <a href="https://github.com/facebook/php-sdk">Facebook PHP SDK</a>.
|
||||||
|
*/
|
||||||
|
abstract class OAuth2Client {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of persistent variables stored.
|
||||||
|
*/
|
||||||
|
protected $conf = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a persistent variable.
|
||||||
|
*
|
||||||
|
* To avoid problems, always use lower case for persistent variable names.
|
||||||
|
*
|
||||||
|
* @param $name
|
||||||
|
* The name of the variable to return.
|
||||||
|
* @param $default
|
||||||
|
* The default value to use if this variable has never been set.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The value of the variable.
|
||||||
|
*/
|
||||||
|
public function getVariable($name, $default = NULL) {
|
||||||
|
return isset($this->conf[$name]) ? $this->conf[$name] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a persistent variable.
|
||||||
|
*
|
||||||
|
* To avoid problems, always use lower case for persistent variable names.
|
||||||
|
*
|
||||||
|
* @param $name
|
||||||
|
* The name of the variable to set.
|
||||||
|
* @param $value
|
||||||
|
* The value to set.
|
||||||
|
*/
|
||||||
|
public function setVariable($name, $value) {
|
||||||
|
$this->conf[$name] = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuff that should get overridden by subclasses.
|
||||||
|
//
|
||||||
|
// I don't want to make these abstract, because then subclasses would have
|
||||||
|
// to implement all of them, which is too much work.
|
||||||
|
//
|
||||||
|
// So they're just stubs. Override the ones you need.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a Drupal OAuth2.0 Application.
|
||||||
|
*
|
||||||
|
* @param $config
|
||||||
|
* An associative array as below:
|
||||||
|
* - base_uri: The base URI for the OAuth2.0 endpoints.
|
||||||
|
* - code: (optional) The authorization code.
|
||||||
|
* - username: (optional) The username.
|
||||||
|
* - password: (optional) The password.
|
||||||
|
* - client_id: (optional) The application ID.
|
||||||
|
* - client_secret: (optional) The application secret.
|
||||||
|
* - authorize_uri: (optional) The end-user authorization endpoint URI.
|
||||||
|
* - access_token_uri: (optional) The token endpoint URI.
|
||||||
|
* - services_uri: (optional) The services endpoint URI.
|
||||||
|
* - cookie_support: (optional) TRUE to enable cookie support.
|
||||||
|
* - base_domain: (optional) The domain for the cookie.
|
||||||
|
* - file_upload_support: (optional) TRUE if file uploads are enabled.
|
||||||
|
*/
|
||||||
|
public function __construct($config = array()) {
|
||||||
|
// We must set base_uri first.
|
||||||
|
$this->setVariable('base_uri', $config['base_uri']);
|
||||||
|
unset($config['base_uri']);
|
||||||
|
|
||||||
|
// Use predefined OAuth2.0 params, or get it from $_REQUEST.
|
||||||
|
foreach (array('code', 'username', 'password') as $name) {
|
||||||
|
if (isset($config[$name]))
|
||||||
|
$this->setVariable($name, $config[$name]);
|
||||||
|
else if (isset($_REQUEST[$name]) && !empty($_REQUEST[$name]))
|
||||||
|
$this->setVariable($name, $_REQUEST[$name]);
|
||||||
|
unset($config[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint URIs.
|
||||||
|
foreach (array('authorize_uri', 'access_token_uri', 'services_uri') as $name) {
|
||||||
|
if (isset($config[$name]))
|
||||||
|
if (substr($config[$name], 0, 4) == "http")
|
||||||
|
$this->setVariable($name, $config[$name]);
|
||||||
|
else
|
||||||
|
$this->setVariable($name, $this->getVariable('base_uri') . $config[$name]);
|
||||||
|
unset($config[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other else configurations.
|
||||||
|
foreach ($config as $name => $value) {
|
||||||
|
$this->setVariable($name, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get session object from custom method.
|
||||||
|
*
|
||||||
|
* By default we generate session object based on access_token response, or
|
||||||
|
* if it is provided from server with $_REQUEST. For sure, if it is provided
|
||||||
|
* by server it should follow our session object format.
|
||||||
|
*
|
||||||
|
* Session object provided by server can ensure the correct expirse and
|
||||||
|
* base_domain setup as predefined in server, also you may get more useful
|
||||||
|
* information for custom functionality, too. BTW, this may require for
|
||||||
|
* additional remote call overhead.
|
||||||
|
*
|
||||||
|
* You may wish to override this function with your custom version due to
|
||||||
|
* your own server-side implementation.
|
||||||
|
*
|
||||||
|
* @param $access_token
|
||||||
|
* (optional) A valid access token in associative array as below:
|
||||||
|
* - access_token: A valid access_token generated by OAuth2.0
|
||||||
|
* authorization endpoint.
|
||||||
|
* - expires_in: (optional) A valid expires_in generated by OAuth2.0
|
||||||
|
* authorization endpoint.
|
||||||
|
* - refresh_token: (optional) A valid refresh_token generated by OAuth2.0
|
||||||
|
* authorization endpoint.
|
||||||
|
* - scope: (optional) A valid scope generated by OAuth2.0
|
||||||
|
* authorization endpoint.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A valid session object in associative array for setup cookie, and
|
||||||
|
* NULL if not able to generate it with custom method.
|
||||||
|
*/
|
||||||
|
protected function getSessionObject($access_token = NULL) {
|
||||||
|
$session = NULL;
|
||||||
|
|
||||||
|
// Try generate local version of session cookie.
|
||||||
|
if (!empty($access_token) && isset($access_token['access_token'])) {
|
||||||
|
$session['access_token'] = $access_token['access_token'];
|
||||||
|
$session['base_domain'] = $this->getVariable('base_domain', OAUTH2_DEFAULT_BASE_DOMAIN);
|
||||||
|
$session['expirse'] = isset($access_token['expires_in']) ? time() + $access_token['expires_in'] : time() + $this->getVariable('expires_in', OAUTH2_DEFAULT_EXPIRES_IN);
|
||||||
|
$session['refresh_token'] = isset($access_token['refresh_token']) ? $access_token['refresh_token'] : '';
|
||||||
|
$session['scope'] = isset($access_token['scope']) ? $access_token['scope'] : '';
|
||||||
|
$session['secret'] = md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
|
||||||
|
|
||||||
|
// Provide our own signature.
|
||||||
|
$sig = self::generateSignature(
|
||||||
|
$session,
|
||||||
|
$this->getVariable('client_secret')
|
||||||
|
);
|
||||||
|
$session['sig'] = $sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading session from $_REQUEST.
|
||||||
|
if (!$session && isset($_REQUEST['session'])) {
|
||||||
|
$session = json_decode(
|
||||||
|
get_magic_quotes_gpc()
|
||||||
|
? stripslashes($_REQUEST['session'])
|
||||||
|
: $_REQUEST['session'],
|
||||||
|
TRUE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an API call.
|
||||||
|
*
|
||||||
|
* Support both OAuth2.0 or normal GET/POST API call, with relative
|
||||||
|
* or absolute URI.
|
||||||
|
*
|
||||||
|
* If no valid OAuth2.0 access token found in session object, this function
|
||||||
|
* will automatically switch as normal remote API call without "oauth_token"
|
||||||
|
* parameter.
|
||||||
|
*
|
||||||
|
* Assume server reply in JSON object and always decode during return. If
|
||||||
|
* you hope to issue a raw query, please use makeRequest().
|
||||||
|
*
|
||||||
|
* @param $path
|
||||||
|
* The target path, relative to base_path/service_uri or an absolute URI.
|
||||||
|
* @param $method
|
||||||
|
* (optional) The HTTP method (default 'GET').
|
||||||
|
* @param $params
|
||||||
|
* (optional The GET/POST parameters.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The JSON decoded response object.
|
||||||
|
*
|
||||||
|
* @throws OAuth2Exception
|
||||||
|
*/
|
||||||
|
public function api($path, $method = 'GET', $params = array()) {
|
||||||
|
if (is_array($method) && empty($params)) {
|
||||||
|
$params = $method;
|
||||||
|
$method = 'GET';
|
||||||
|
}
|
||||||
|
|
||||||
|
// json_encode all params values that are not strings.
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
if (!is_string($value)) {
|
||||||
|
$params[$key] = json_encode($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = json_decode($this->makeOAuth2Request(
|
||||||
|
$this->getUri($path),
|
||||||
|
$method,
|
||||||
|
$params
|
||||||
|
), TRUE);
|
||||||
|
|
||||||
|
// Results are returned, errors are thrown.
|
||||||
|
if (is_array($result) && isset($result['error'])) {
|
||||||
|
$e = new OAuth2Exception($result);
|
||||||
|
switch ($e->getType()) {
|
||||||
|
// OAuth 2.0 Draft 10 style.
|
||||||
|
case 'invalid_token':
|
||||||
|
$this->setSession(NULL);
|
||||||
|
default:
|
||||||
|
$this->setSession(NULL);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End stuff that should get overridden.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default options for cURL.
|
||||||
|
*/
|
||||||
|
public static $CURL_OPTS = array(
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 10,
|
||||||
|
CURLOPT_RETURNTRANSFER => TRUE,
|
||||||
|
CURLOPT_HEADER => TRUE,
|
||||||
|
CURLOPT_TIMEOUT => 60,
|
||||||
|
CURLOPT_USERAGENT => 'oauth2-draft-v10',
|
||||||
|
CURLOPT_HTTPHEADER => array("Accept: application/json"),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Session.
|
||||||
|
*
|
||||||
|
* @param $session
|
||||||
|
* (optional) The session object to be set. NULL if hope to frush existing
|
||||||
|
* session object.
|
||||||
|
* @param $write_cookie
|
||||||
|
* (optional) TRUE if a cookie should be written. This value is ignored
|
||||||
|
* if cookie support has been disabled.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The current OAuth2.0 client-side instance.
|
||||||
|
*/
|
||||||
|
public function setSession($session = NULL, $write_cookie = TRUE) {
|
||||||
|
$this->setVariable('_session', $this->validateSessionObject($session));
|
||||||
|
$this->setVariable('_session_loaded', TRUE);
|
||||||
|
if ($write_cookie) {
|
||||||
|
$this->setCookieFromSession($this->getVariable('_session'));
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the session object.
|
||||||
|
*
|
||||||
|
* This will automatically look for a signed session via custom method,
|
||||||
|
* OAuth2.0 grant type with authorization_code, OAuth2.0 grant type with
|
||||||
|
* password, or cookie that we had already setup.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The valid session object with OAuth2.0 infomration, and NULL if not
|
||||||
|
* able to discover any cases.
|
||||||
|
*/
|
||||||
|
public function getSession() {
|
||||||
|
if (!$this->getVariable('_session_loaded')) {
|
||||||
|
$session = NULL;
|
||||||
|
$write_cookie = TRUE;
|
||||||
|
|
||||||
|
// Try obtain login session by custom method.
|
||||||
|
$session = $this->getSessionObject(NULL);
|
||||||
|
$session = $this->validateSessionObject($session);
|
||||||
|
|
||||||
|
// grant_type == authorization_code.
|
||||||
|
if (!$session && $this->getVariable('code')) {
|
||||||
|
$access_token = $this->getAccessTokenFromAuthorizationCode($this->getVariable('code'));
|
||||||
|
$session = $this->getSessionObject($access_token);
|
||||||
|
$session = $this->validateSessionObject($session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// grant_type == password.
|
||||||
|
if (!$session && $this->getVariable('username') && $this->getVariable('password')) {
|
||||||
|
$access_token = $this->getAccessTokenFromPassword($this->getVariable('username'), $this->getVariable('password'));
|
||||||
|
$session = $this->getSessionObject($access_token);
|
||||||
|
$session = $this->validateSessionObject($session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading session from cookie if necessary.
|
||||||
|
if (!$session && $this->getVariable('cookie_support')) {
|
||||||
|
$cookie_name = $this->getSessionCookieName();
|
||||||
|
if (isset($_COOKIE[$cookie_name])) {
|
||||||
|
$session = array();
|
||||||
|
parse_str(trim(
|
||||||
|
get_magic_quotes_gpc()
|
||||||
|
? stripslashes($_COOKIE[$cookie_name])
|
||||||
|
: $_COOKIE[$cookie_name],
|
||||||
|
'"'
|
||||||
|
), $session);
|
||||||
|
$session = $this->validateSessionObject($session);
|
||||||
|
// Write only if we need to delete a invalid session cookie.
|
||||||
|
$write_cookie = empty($session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setSession($session, $write_cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getVariable('_session');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an OAuth2.0 access token from session.
|
||||||
|
*
|
||||||
|
* This will trigger getSession() and so we MUST initialize with required
|
||||||
|
* configuration.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The valid OAuth2.0 access token, and NULL if not exists in session.
|
||||||
|
*/
|
||||||
|
public function getAccessToken() {
|
||||||
|
$session = $this->getSession();
|
||||||
|
return isset($session['access_token']) ? $session['access_token'] : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get access token from OAuth2.0 token endpoint with authorization code.
|
||||||
|
*
|
||||||
|
* This function will only be activated if both access token URI, client
|
||||||
|
* identifier and client secret are setup correctly.
|
||||||
|
*
|
||||||
|
* @param $code
|
||||||
|
* Authorization code issued by authorization server's authorization
|
||||||
|
* endpoint.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A valid OAuth2.0 JSON decoded access token in associative array, and
|
||||||
|
* NULL if not enough parameters or JSON decode failed.
|
||||||
|
*/
|
||||||
|
private function getAccessTokenFromAuthorizationCode($code) {
|
||||||
|
if ($this->getVariable('access_token_uri') && $this->getVariable('client_id') && $this->getVariable('client_secret')) {
|
||||||
|
return json_decode($this->makeRequest(
|
||||||
|
$this->getVariable('access_token_uri'),
|
||||||
|
'POST',
|
||||||
|
array(
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => $this->getVariable('client_id'),
|
||||||
|
'client_secret' => $this->getVariable('client_secret'),
|
||||||
|
'code' => $code,
|
||||||
|
'redirect_uri' => $this->getCurrentUri(),
|
||||||
|
)
|
||||||
|
), TRUE);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get access token from OAuth2.0 token endpoint with basic user
|
||||||
|
* credentials.
|
||||||
|
*
|
||||||
|
* This function will only be activated if both username and password
|
||||||
|
* are setup correctly.
|
||||||
|
*
|
||||||
|
* @param $username
|
||||||
|
* Username to be check with.
|
||||||
|
* @param $password
|
||||||
|
* Password to be check with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A valid OAuth2.0 JSON decoded access token in associative array, and
|
||||||
|
* NULL if not enough parameters or JSON decode failed.
|
||||||
|
*/
|
||||||
|
private function getAccessTokenFromPassword($username, $password) {
|
||||||
|
if ($this->getVariable('access_token_uri') && $this->getVariable('client_id') && $this->getVariable('client_secret')) {
|
||||||
|
return json_decode($this->makeRequest(
|
||||||
|
$this->getVariable('access_token_uri'),
|
||||||
|
'POST',
|
||||||
|
array(
|
||||||
|
'grant_type' => 'password',
|
||||||
|
'client_id' => $this->getVariable('client_id'),
|
||||||
|
'client_secret' => $this->getVariable('client_secret'),
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password,
|
||||||
|
)
|
||||||
|
), TRUE);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an OAuth2.0 Request.
|
||||||
|
*
|
||||||
|
* Automatically append "oauth_token" in query parameters if not yet
|
||||||
|
* exists and able to discover a valid access token from session. Otherwise
|
||||||
|
* just ignore setup with "oauth_token" and handle the API call AS-IS, and
|
||||||
|
* so may issue a plain API call without OAuth2.0 protection.
|
||||||
|
*
|
||||||
|
* @param $path
|
||||||
|
* The target path, relative to base_path/service_uri or an absolute URI.
|
||||||
|
* @param $method
|
||||||
|
* (optional) The HTTP method (default 'GET').
|
||||||
|
* @param $params
|
||||||
|
* (optional The GET/POST parameters.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The JSON decoded response object.
|
||||||
|
*
|
||||||
|
* @throws OAuth2Exception
|
||||||
|
*/
|
||||||
|
protected function makeOAuth2Request($path, $method = 'GET', $params = array()) {
|
||||||
|
if ((!isset($params['oauth_token']) || empty($params['oauth_token'])) && $oauth_token = $this->getAccessToken()) {
|
||||||
|
$params['oauth_token'] = $oauth_token;
|
||||||
|
}
|
||||||
|
return $this->makeRequest($path, $method, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an HTTP request.
|
||||||
|
*
|
||||||
|
* This method can be overriden by subclasses if developers want to do
|
||||||
|
* fancier things or use something other than cURL to make the request.
|
||||||
|
*
|
||||||
|
* @param $path
|
||||||
|
* The target path, relative to base_path/service_uri or an absolute URI.
|
||||||
|
* @param $method
|
||||||
|
* (optional) The HTTP method (default 'GET').
|
||||||
|
* @param $params
|
||||||
|
* (optional The GET/POST parameters.
|
||||||
|
* @param $ch
|
||||||
|
* (optional) An initialized curl handle
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The JSON decoded response object.
|
||||||
|
*/
|
||||||
|
protected function makeRequest($path, $method = 'GET', $params = array(), $ch = NULL) {
|
||||||
|
if (!$ch)
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
$opts = self::$CURL_OPTS;
|
||||||
|
if ($params) {
|
||||||
|
switch ($method) {
|
||||||
|
case 'GET':
|
||||||
|
$path .= '?' . http_build_query($params, NULL, '&');
|
||||||
|
break;
|
||||||
|
// Method override as we always do a POST.
|
||||||
|
default:
|
||||||
|
if ($this->getVariable('file_upload_support')) {
|
||||||
|
$opts[CURLOPT_POSTFIELDS] = $params;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$opts[CURLOPT_POSTFIELDS] = http_build_query($params, NULL, '&');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$opts[CURLOPT_URL] = $path;
|
||||||
|
|
||||||
|
// Disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
|
||||||
|
// for 2 seconds if the server does not support this header.
|
||||||
|
if (isset($opts[CURLOPT_HTTPHEADER])) {
|
||||||
|
$existing_headers = $opts[CURLOPT_HTTPHEADER];
|
||||||
|
$existing_headers[] = 'Expect:';
|
||||||
|
$opts[CURLOPT_HTTPHEADER] = $existing_headers;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$opts[CURLOPT_HTTPHEADER] = array('Expect:');
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt_array($ch, $opts);
|
||||||
|
$result = curl_exec($ch);
|
||||||
|
|
||||||
|
if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
|
||||||
|
error_log('Invalid or no certificate authority found, using bundled information');
|
||||||
|
curl_setopt($ch, CURLOPT_CAINFO,
|
||||||
|
dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
|
||||||
|
$result = curl_exec($ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result === FALSE) {
|
||||||
|
$e = new OAuth2Exception(array(
|
||||||
|
'code' => curl_errno($ch),
|
||||||
|
'message' => curl_error($ch),
|
||||||
|
));
|
||||||
|
curl_close($ch);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Split the HTTP response into header and body.
|
||||||
|
list($headers, $body) = explode("\r\n\r\n", $result);
|
||||||
|
$headers = explode("\r\n", $headers);
|
||||||
|
|
||||||
|
// We catch HTTP/1.1 4xx or HTTP/1.1 5xx error response.
|
||||||
|
if (strpos($headers[0], 'HTTP/1.1 4') !== FALSE || strpos($headers[0], 'HTTP/1.1 5') !== FALSE) {
|
||||||
|
$result = array(
|
||||||
|
'code' => 0,
|
||||||
|
'message' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (preg_match('/^HTTP\/1.1 ([0-9]{3,3}) (.*)$/', $headers[0], $matches)) {
|
||||||
|
$result['code'] = $matches[1];
|
||||||
|
$result['message'] = $matches[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case retrun with WWW-Authenticate replace the description.
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
if (preg_match("/^WWW-Authenticate:.*error='(.*)'/", $header, $matches)) {
|
||||||
|
$result['error'] = $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_encode($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the cookie that contains the session object.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The cookie name.
|
||||||
|
*/
|
||||||
|
private function getSessionCookieName() {
|
||||||
|
return 'oauth2_' . $this->getVariable('client_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a JS Cookie based on the _passed in_ session.
|
||||||
|
*
|
||||||
|
* It does not use the currently stored session - you need to explicitly
|
||||||
|
* pass it in.
|
||||||
|
*
|
||||||
|
* @param $session
|
||||||
|
* The session to use for setting the cookie.
|
||||||
|
*/
|
||||||
|
protected function setCookieFromSession($session = NULL) {
|
||||||
|
if (!$this->getVariable('cookie_support'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$cookie_name = $this->getSessionCookieName();
|
||||||
|
$value = 'deleted';
|
||||||
|
$expires = time() - 3600;
|
||||||
|
$base_domain = $this->getVariable('base_domain', OAUTH2_DEFAULT_BASE_DOMAIN);
|
||||||
|
if ($session) {
|
||||||
|
$value = '"' . http_build_query($session, NULL, '&') . '"';
|
||||||
|
$base_domain = isset($session['base_domain']) ? $session['base_domain'] : $base_domain;
|
||||||
|
$expires = isset($session['expires']) ? $session['expires'] : time() + $this->getVariable('expires_in', OAUTH2_DEFAULT_EXPIRES_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend dot if a domain is found.
|
||||||
|
if ($base_domain)
|
||||||
|
$base_domain = '.' . $base_domain;
|
||||||
|
|
||||||
|
// If an existing cookie is not set, we dont need to delete it.
|
||||||
|
if ($value == 'deleted' && empty($_COOKIE[$cookie_name]))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (headers_sent())
|
||||||
|
error_log('Could not set cookie. Headers already sent.');
|
||||||
|
else
|
||||||
|
setcookie($cookie_name, $value, $expires, '/', $base_domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a session_version = 3 style session object.
|
||||||
|
*
|
||||||
|
* @param $session
|
||||||
|
* The session object.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The session object if it validates, NULL otherwise.
|
||||||
|
*/
|
||||||
|
protected function validateSessionObject($session) {
|
||||||
|
// Make sure some essential fields exist.
|
||||||
|
if (is_array($session) && isset($session['access_token']) && isset($session['sig'])) {
|
||||||
|
// Validate the signature.
|
||||||
|
$session_without_sig = $session;
|
||||||
|
unset($session_without_sig['sig']);
|
||||||
|
|
||||||
|
$expected_sig = self::generateSignature(
|
||||||
|
$session_without_sig,
|
||||||
|
$this->getVariable('client_secret')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($session['sig'] != $expected_sig) {
|
||||||
|
error_log('Got invalid session signature in cookie.');
|
||||||
|
$session = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$session = NULL;
|
||||||
|
}
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since $_SERVER['REQUEST_URI'] is only available on Apache, we
|
||||||
|
* generate an equivalent using other environment variables.
|
||||||
|
*/
|
||||||
|
function getRequestUri() {
|
||||||
|
if (isset($_SERVER['REQUEST_URI'])) {
|
||||||
|
$uri = $_SERVER['REQUEST_URI'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isset($_SERVER['argv'])) {
|
||||||
|
$uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['argv'][0];
|
||||||
|
}
|
||||||
|
elseif (isset($_SERVER['QUERY_STRING'])) {
|
||||||
|
$uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$uri = $_SERVER['SCRIPT_NAME'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Prevent multiple slashes to avoid cross site requests via the Form API.
|
||||||
|
$uri = '/' . ltrim($uri, '/');
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current URL.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The current URL.
|
||||||
|
*/
|
||||||
|
protected function getCurrentUri() {
|
||||||
|
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
|
||||||
|
? 'https://'
|
||||||
|
: 'http://';
|
||||||
|
$current_uri = $protocol . $_SERVER['HTTP_HOST'] . $this->getRequestUri();
|
||||||
|
$parts = parse_url($current_uri);
|
||||||
|
|
||||||
|
$query = '';
|
||||||
|
if (!empty($parts['query'])) {
|
||||||
|
$params = array();
|
||||||
|
parse_str($parts['query'], $params);
|
||||||
|
$params = array_filter($params);
|
||||||
|
if (!empty($params)) {
|
||||||
|
$query = '?' . http_build_query($params, NULL, '&');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use port if non default.
|
||||||
|
$port = isset($parts['port']) &&
|
||||||
|
(($protocol === 'http://' && $parts['port'] !== 80) || ($protocol === 'https://' && $parts['port'] !== 443))
|
||||||
|
? ':' . $parts['port'] : '';
|
||||||
|
|
||||||
|
// Rebuild.
|
||||||
|
return $protocol . $parts['host'] . $port . $parts['path'] . $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the URL for given path and parameters.
|
||||||
|
*
|
||||||
|
* @param $path
|
||||||
|
* (optional) The path.
|
||||||
|
* @param $params
|
||||||
|
* (optional) The query parameters in associative array.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The URL for the given parameters.
|
||||||
|
*/
|
||||||
|
protected function getUri($path = '', $params = array()) {
|
||||||
|
$url = $this->getVariable('services_uri') ? $this->getVariable('services_uri') : $this->getVariable('base_uri');
|
||||||
|
|
||||||
|
if (!empty($path))
|
||||||
|
if (substr($path, 0, 4) == "http")
|
||||||
|
$url = $path;
|
||||||
|
else
|
||||||
|
$url = rtrim($url, '/') . '/' . ltrim($path, '/');
|
||||||
|
|
||||||
|
if (!empty($params))
|
||||||
|
$url .= '?' . http_build_query($params, NULL, '&');
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a signature for the given params and secret.
|
||||||
|
*
|
||||||
|
* @param $params
|
||||||
|
* The parameters to sign.
|
||||||
|
* @param $secret
|
||||||
|
* The secret to sign with.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The generated signature
|
||||||
|
*/
|
||||||
|
protected function generateSignature($params, $secret) {
|
||||||
|
// Work with sorted data.
|
||||||
|
ksort($params);
|
||||||
|
|
||||||
|
// Generate the base string.
|
||||||
|
$base_string = '';
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
$base_string .= $key . '=' . $value;
|
||||||
|
}
|
||||||
|
$base_string .= $secret;
|
||||||
|
|
||||||
|
return md5($base_string);
|
||||||
|
}
|
||||||
|
}
|
85
library/oauth2-php/lib/OAuth2Exception.inc
Normal file
85
library/oauth2-php/lib/OAuth2Exception.inc
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth2.0 draft v10 exception handling.
|
||||||
|
*
|
||||||
|
* @author Originally written by Naitik Shah <naitik@facebook.com>.
|
||||||
|
* @author Update to draft v10 by Edison Wong <hswong3i@pantarei-design.com>.
|
||||||
|
*
|
||||||
|
* @sa <a href="https://github.com/facebook/php-sdk">Facebook PHP SDK</a>.
|
||||||
|
*/
|
||||||
|
class OAuth2Exception extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result from the API server that represents the exception information.
|
||||||
|
*/
|
||||||
|
protected $result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a new API Exception with the given result.
|
||||||
|
*
|
||||||
|
* @param $result
|
||||||
|
* The result from the API server.
|
||||||
|
*/
|
||||||
|
public function __construct($result) {
|
||||||
|
$this->result = $result;
|
||||||
|
|
||||||
|
$code = isset($result['code']) ? $result['code'] : 0;
|
||||||
|
|
||||||
|
if (isset($result['error'])) {
|
||||||
|
// OAuth 2.0 Draft 10 style
|
||||||
|
$message = $result['error'];
|
||||||
|
}
|
||||||
|
elseif (isset($result['message'])) {
|
||||||
|
// cURL style
|
||||||
|
$message = $result['message'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$message = 'Unknown Error. Check getResult()';
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($message, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the associated result object returned by the API server.
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
* The result from the API server.
|
||||||
|
*/
|
||||||
|
public function getResult() {
|
||||||
|
return $this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the associated type for the error. This will default to
|
||||||
|
* 'Exception' when a type is not available.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The type for the error.
|
||||||
|
*/
|
||||||
|
public function getType() {
|
||||||
|
if (isset($this->result['error'])) {
|
||||||
|
$message = $this->result['error'];
|
||||||
|
if (is_string($message)) {
|
||||||
|
// OAuth 2.0 Draft 10 style
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'Exception';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To make debugging easier.
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
* The string representation of the error.
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
$str = $this->getType() . ': ';
|
||||||
|
if ($this->code != 0) {
|
||||||
|
$str .= $this->code . ': ';
|
||||||
|
}
|
||||||
|
return $str . $this->message;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue