Merge branch 'oauthapi'

This commit is contained in:
Fabio Comuni 2011-11-07 17:41:23 +01:00
commit 00c342e13d
26 changed files with 3183 additions and 8 deletions

View file

@ -654,7 +654,7 @@ function get_guid($size=16) {
// returns the complete html for inserting into the page
if(! function_exists('login')) {
function login($register = false) {
function login($register = false, $hiddens=false) {
$o = "";
$reg = false;
if ($register) {
@ -685,6 +685,7 @@ function login($register = false) {
'$openid' => !$noid,
'$lopenid' => array('openid_url', t('OpenID: '),'',''),
'$hiddens' => $hiddens,
'$register' => $reg,

BIN
images/icons/10/plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

BIN
images/icons/16/plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

BIN
images/icons/22/plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

BIN
images/icons/48/plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -2,7 +2,7 @@
IMAGES=add.png edit.png gear.png info.png menu.png \
notify_off.png star.png delete.png feed.png group.png \
lock.png notice.png notify_on.png user.png link.png \
play.png
play.png plugin.png
DESTS=10/ 16/ 22/ 48/ \
$(addprefix 10/, $(IMAGES)) \

BIN
images/icons/plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -2,7 +2,7 @@
require_once("bbcode.php");
require_once("datetime.php");
require_once("conversation.php");
require_once("oauth.php");
/*
* Twitter-Like API
*
@ -27,6 +27,23 @@
* Simple HTTP Login
*/
function api_login(&$a){
// login with oauth
try{
$oauth = new FKOAuth1();
list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request());
if (!is_null($token)){
$oauth->loginUser($token->uid);
call_hooks('logged_in', $a->user);
return;
}
echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die();
}catch(Exception $e){
logger(__file__.__line__.__function__."\n".$e);
//die(__file__.__line__.__function__."<pre>".$e); die();
}
// workaround for HTTP-auth in CGI mode
if(x($_SERVER,'REDIRECT_REMOTE_USER')) {
$userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ;
@ -1127,3 +1144,31 @@
}
api_register_func('api/direct_messages/sent','api_direct_messages_sentbox',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 $r;
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 $r;
killme();
}
api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);

266
include/oauth.php Normal file
View file

@ -0,0 +1,266 @@
<?php
/**
* OAuth server
* Based on oauth2-php <http://code.google.com/p/oauth2-php/>
*
*/
define('REQUEST_TOKEN_DURATION', 300);
define('ACCESS_TOKEN_DURATION', 31536000);
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) {
logger(__function__.":".$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) {
logger(__function__.":".$consumer.", ". $token_type.", ".$token);
$r = q("SELECT id, secret,scope, expires, uid 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'];
$ot->uid = $r[0]['uid'];
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) {
logger(__function__.":".$consumer.", ". $callback);
$key = $this->gen_token();
$sec = $this->gen_token();
if ($consumer->key){
$k = $consumer->key;
} else {
$k = $consumer;
}
$r = q("INSERT INTO tokens (id, secret, client_id, scope, expires) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d)",
dbesc($key),
dbesc($sec),
dbesc($k),
'request',
intval(REQUEST_TOKEN_DURATION));
if (!$r) return null;
return new OAuthToken($key,$sec);
}
function new_access_token($token, $consumer, $verifier = null) {
logger(__function__.":".$token.", ". $consumer.", ". $verifier);
// 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;
// get user for this verifier
$uverifier = get_config("oauth", $verifier);
logger(__function__.":".$verifier.",".$uverifier);
if (is_null($verifier) || ($uverifier!==false)){
$key = $this->gen_token();
$sec = $this->gen_token();
$r = q("INSERT INTO tokens (id, secret, client_id, scope, expires, uid) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d, %d)",
dbesc($key),
dbesc($sec),
dbesc($consumer->key),
'access',
intval(ACCESS_TOKEN_DURATION),
intval($uverifier));
if ($r)
$ret = new OAuthToken($key,$sec);
}
q("DELETE FROM tokens WHERE id='%s'", $token->key);
if (!is_null($ret) && $uverifier!==false){
del_config("oauth", $verifier);
/* $apps = get_pconfig($uverifier, "oauth", "apps");
if ($apps===false) $apps=array();
$apps[] = $consumer->key;
set_pconfig($uverifier, "oauth", "apps", $apps);*/
}
return $ret;
}
}
class FKOAuth1 extends OAuthServer {
function __construct() {
parent::__construct(new FKOAuthDataStore());
$this->add_signature_method(new OAuthSignatureMethod_PLAINTEXT());
$this->add_signature_method(new OAuthSignatureMethod_HMAC_SHA1());
}
function loginUser($uid){
logger("FKOAuth1::loginUser $uid");
$a = get_app();
$r = q("SELECT * FROM `user` WHERE uid=%d AND `blocked` = 0 AND `account_expired` = 0 AND `verified` = 1 LIMIT 1",
intval($uid)
);
if(count($r)){
$record = $r[0];
} else {
logger('FKOAuth1::loginUser failure: ' . print_r($_SERVER,true), LOGGER_DEBUG);
header('HTTP/1.0 401 Unauthorized');
die('This api requires login');
}
$_SESSION['uid'] = $record['uid'];
$_SESSION['theme'] = $record['theme'];
$_SESSION['authenticated'] = 1;
$_SESSION['page_flags'] = $record['page-flags'];
$_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $record['nickname'];
$_SESSION['addr'] = $_SERVER['REMOTE_ADDR'];
//notice( t("Welcome back ") . $record['username'] . EOL);
$a->user = $record;
if(strlen($a->user['timezone'])) {
date_default_timezone_set($a->user['timezone']);
$a->timezone = $a->user['timezone'];
}
$r = q("SELECT * FROM `contact` WHERE `uid` = %s AND `self` = 1 LIMIT 1",
intval($_SESSION['uid']));
if(count($r)) {
$a->contact = $r[0];
$a->cid = $r[0]['id'];
$_SESSION['cid'] = $a->cid;
}
q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d LIMIT 1",
dbesc(datetime_convert()),
intval($_SESSION['uid'])
);
call_hooks('logged_in', $a->user);
}
}
/*
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;
}
}
*/

View file

@ -27,6 +27,10 @@ class OAuthToken {
public $key;
public $secret;
public $expires;
public $scope;
public $uid;
/**
* key = the token
* secret = the token secret
@ -85,7 +89,8 @@ abstract class OAuthSignatureMethod {
*/
public function check_signature($request, $consumer, $token, $signature) {
$built = $this->build_signature($request, $consumer, $token);
return $built == $signature;
//echo "<pre>"; var_dump($signature, $built, ($built == $signature)); killme();
return ($built == $signature);
}
}
@ -113,7 +118,9 @@ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
$key = implode('&', $key_parts);
return base64_encode(hash_hmac('sha1', $base_string, $key, true));
$r = base64_encode(hash_hmac('sha1', $base_string, $key, true));
return $r;
}
}
@ -282,7 +289,12 @@ class OAuthRequest {
}
}
// fix for friendika redirect system
$http_url = substr($http_url, 0, strpos($http_url,$parameters['q'])+strlen($parameters['q']));
unset( $parameters['q'] );
//echo "<pre>".__function__."\n"; var_dump($http_method, $http_url, $parameters, $_SERVER['REQUEST_URI']); killme();
return new OAuthRequest($http_method, $http_url, $parameters);
}
@ -544,6 +556,7 @@ class OAuthServer {
public function verify_request(&$request) {
$this->get_version($request);
$consumer = $this->get_consumer($request);
//echo __file__.__line__.__function__."<pre>"; var_dump($consumer); die();
$token = $this->get_token($request, $consumer, "access");
$this->check_signature($request, $consumer, $token);
return array($consumer, $token);
@ -642,6 +655,7 @@ class OAuthServer {
$token,
$signature
);
if (!$valid_sig) {
throw new OAuthException("Invalid signature");

View 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.

View 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.

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

View 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);
}
}

View 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.

View file

@ -2,7 +2,115 @@
require_once('include/api.php');
function oauth_get_client($request){
$params = $request->get_parameters();
$token = $params['oauth_token'];
$r = q("SELECT `clients`.*
FROM `clients`, `tokens`
WHERE `clients`.`client_id`=`tokens`.`client_id`
AND `tokens`.`id`='%s' AND `tokens`.`scope`='request'",
dbesc($token));
if (!count($r))
return null;
return $r[0];
}
function api_post(&$a) {
if(! local_user()) {
notice( t('Permission denied.') . EOL);
return;
}
if(count($a->user) && x($a->user,'uid') && $a->user['uid'] != local_user()) {
notice( t('Permission denied.') . EOL);
return;
}
}
function api_content(&$a) {
if ($a->cmd=='api/oauth/authorize'){
/*
* api/oauth/authorize interact with the user. return a standard page
*/
$a->page['template'] = "minimal";
// get consumer/client from request token
try {
$request = OAuthRequest::from_request();
} catch(Exception $e) {
echo "<pre>"; var_dump($e); killme();
}
if (x($_POST,'oauth_yes')){
$app = oauth_get_client($request);
if (is_null($app)) return "Invalid request. Unknown token.";
$consumer = new OAuthConsumer($app['client_id'], $app['pw'], $app['redirect_uri']);
$verifier = md5($app['secret'].local_user());
set_config("oauth", $verifier, local_user());
if ($consumer->callback_url!=null) {
$params = $request->get_parameters();
$glue="?";
if (strstr($consumer->callback_url,$glue)) $glue="?";
goaway($consumer->callback_url.$glue."oauth_token=".OAuthUtil::urlencode_rfc3986($params['oauth_token'])."&oauth_verifier=".OAuthUtil::urlencode_rfc3986($verifier));
killme();
}
$tpl = get_markup_template("oauth_authorize_done.tpl");
$o = replace_macros($tpl, array(
'$title' => t('Authorize application connection'),
'$info' => t('Return to your app and insert this Securty Code:'),
'$code' => $verifier,
));
return $o;
}
if(! local_user()) {
//TODO: we need login form to redirect to this page
notice( t('Please login to continue.') . EOL );
return login(false,$request->get_parameters());
}
//FKOAuth1::loginUser(4);
$app = oauth_get_client($request);
if (is_null($app)) return "Invalid request. Unknown token.";
$tpl = get_markup_template('oauth_authorize.tpl');
$o = replace_macros($tpl, array(
'$title' => t('Authorize application connection'),
'$app' => $app,
'$authorize' => t('Do you want to authorize this application to access your posts and contacts, and/or create new posts for you?'),
'$yes' => t('Yes'),
'$no' => t('No'),
));
//echo "<pre>"; var_dump($app); killme();
return $o;
}
echo api_call($a);
killme();
}

20
mod/notice.php Normal file
View file

@ -0,0 +1,20 @@
<?php
/* identi.ca -> friendika items permanent-url compatibility */
function notice_init(&$a){
$id = $a->argv[1];
$r = q("SELECT user.nickname FROM user LEFT JOIN item ON item.uid=user.uid WHERE item.id=%d",
intval($id)
);
if (count($r)){
$nick = $r[0]['nickname'];
$url = $a->get_baseurl()."/display/$nick/$id";
goaway($url);
} else {
$a->error = 404;
notice( t('Item not found.') . EOL);
}
return;
}

View file

@ -47,6 +47,58 @@ function settings_post(&$a) {
return;
}
if(($a->argc > 1) && ($a->argv[1] === 'oauth') && x($_POST,'remove')){
$key = $_POST['remove'];
q("DELETE FROM tokens WHERE id='%s' AND uid=%d",
dbesc($key),
local_user());
goaway($a->get_baseurl()."/settings/oauth/");
return;
}
if(($a->argc > 2) && ($a->argv[1] === 'oauth') && ($a->argv[2] === 'edit') && x($_POST,'submit')) {
$name = ((x($_POST,'name')) ? $_POST['name'] : '');
$key = ((x($_POST,'key')) ? $_POST['key'] : '');
$secret = ((x($_POST,'secret')) ? $_POST['secret'] : '');
$redirect = ((x($_POST,'redirect')) ? $_POST['redirect'] : '');
$icon = ((x($_POST,'icon')) ? $_POST['icon'] : '');
if ($name=="" || $key=="" || $secret==""){
notice(t("Missing some important data!"));
} else {
if ($_POST['submit']==t("Update")){
$r = q("UPDATE clients SET
client_id='%s',
pw='%s',
name='%s',
redirect_uri='%s',
icon='%s',
uid=%d
WHERE client_id='%s'",
dbesc($key),
dbesc($secret),
dbesc($name),
dbesc($redirect),
dbesc($icon),
local_user(),
dbesc($key));
} else {
$r = q("INSERT INTO clients
(client_id, pw, name, redirect_uri, icon, uid)
VALUES ('%s','%s','%s','%s','%s',%d)",
dbesc($key),
dbesc($secret),
dbesc($name),
dbesc($redirect),
dbesc($icon),
local_user());
}
}
goaway($a->get_baseurl()."/settings/oauth/");
return;
}
if(($a->argc > 1) && ($a->argv[1] == 'addon')) {
call_hooks('plugin_settings_post', $_POST);
return;
@ -341,6 +393,11 @@ function settings_content(&$a) {
'url' => $a->get_baseurl().'/settings/addon',
'sel' => (($a->argc > 1) && ($a->argv[1] === 'addon')?'active':''),
),
array(
'label' => t('Connections'),
'url' => $a->get_baseurl() . '/settings/oauth',
'sel' => (($a->argc > 1) && ($a->argv[1] === 'oauth')?'active':''),
),
array(
'label' => t('Export personal data'),
'url' => $a->get_baseurl() . '/uexport',
@ -353,8 +410,83 @@ function settings_content(&$a) {
'$tabs' => $tabs,
));
if(($a->argc > 1) && ($a->argv[1] === 'oauth')) {
if(($a->argc > 2) && ($a->argv[2] === 'add')) {
$tpl = get_markup_template("settings_oauth_edit.tpl");
$o .= replace_macros($tpl, array(
'$tabs' => $tabs,
'$title' => t('Add application'),
'$submit' => t('Submit'),
'$cancel' => t('Cancel'),
'$name' => array('name', t('Name'), '', ''),
'$key' => array('key', t('Consumer Key'), '', ''),
'$secret' => array('secret', t('Consumer Secret'), '', ''),
'$redirect' => array('redirect', t('Redirect'), '', ''),
'$icon' => array('icon', t('Icon url'), '', ''),
));
return $o;
}
if(($a->argc > 3) && ($a->argv[2] === 'edit')) {
$r = q("SELECT * FROM clients WHERE client_id='%s' AND uid=%d",
dbesc($a->argv[3]),
local_user());
if (!count($r)){
notice(t("You can't edit this application."));
return;
}
$app = $r[0];
$tpl = get_markup_template("settings_oauth_edit.tpl");
$o .= replace_macros($tpl, array(
'$tabs' => $tabs,
'$title' => t('Add application'),
'$submit' => t('Update'),
'$cancel' => t('Cancel'),
'$name' => array('name', t('Name'), $app['name'] , ''),
'$key' => array('key', t('Consumer Key'), $app['client_id'], ''),
'$secret' => array('secret', t('Consumer Secret'), $app['pw'], ''),
'$redirect' => array('redirect', t('Redirect'), $app['redirect_uri'], ''),
'$icon' => array('icon', t('Icon url'), $app['icon'], ''),
));
return $o;
}
if(($a->argc > 3) && ($a->argv[2] === 'delete')) {
$r = q("DELETE FROM clients WHERE client_id='%s' AND uid=%d",
dbesc($a->argv[3]),
local_user());
goaway($a->get_baseurl()."/settings/oauth/");
return;
}
$r = q("SELECT clients.*, tokens.id as oauth_token, (clients.uid=%d) AS my
FROM clients
LEFT JOIN tokens ON clients.client_id=tokens.client_id
WHERE clients.uid IN (%d,0)",
local_user(),
local_user());
$tpl = get_markup_template("settings_oauth.tpl");
$o .= replace_macros($tpl, array(
'$baseurl' => $a->get_baseurl(),
'$title' => t('Connected Apps'),
'$add' => t('Add application'),
'$edit' => t('Edit'),
'$delete' => t('Delete'),
'$consumerkey' => t('Client key starts with'),
'$noname' => t('No name'),
'$remove' => t('Remove authorization'),
'$tabs' => $tabs,
'$apps' => $r,
));
return $o;
}
if(($a->argc > 1) && ($a->argv[1] === 'addon')) {
$settings_addons = "";

View file

@ -22,6 +22,11 @@
<input type="submit" name="submit" id="login-submit-button" value="$login" />
</div>
{{ for $hiddens as $k=>$v }}
<input type="hidden" name="$k" value="$v" />
{{ endfor }}
</form>

14
view/minimal.php Normal file
View file

@ -0,0 +1,14 @@
<!DOCTYPE html >
<html>
<head>
<title><?php if(x($page,'title')) echo $page['title'] ?></title>
<script>var baseurl="<?php echo $a->get_baseurl() ?>";</script>
<?php if(x($page,'htmlhead')) echo $page['htmlhead'] ?>
</head>
<body>
<section style="margin:0px!important; padding:0px!important; float:none!important;display:block!important;"><?php if(x($page,'content')) echo $page['content']; ?>
<div id="page-footer"></div>
</section>
</body>
</html>

10
view/oauth_authorize.tpl Normal file
View file

@ -0,0 +1,10 @@
<h1>$title</h1>
<div class='oauthapp'>
<img src='$app.icon'>
<h4>$app.name</h4>
</div>
<h3>$authorize</h3>
<form method="POST">
<div class="settings-submit-wrapper"><input class="settings-submit" type="submit" name="oauth_yes" value="$yes" /></div>
</form>

View file

@ -0,0 +1,4 @@
<h1>$title</h1>
<p>$info</p>
<code>$code</code>

32
view/settings_oauth.tpl Normal file
View file

@ -0,0 +1,32 @@
$tabs
<h1>$title</h1>
<form action="settings/oauth" method="post" autocomplete="off">
<div id="profile-edit-links">
<ul>
<li>
<a id="profile-edit-view-link" href="$baseurl/settings/oauth/add">$add</a>
</li>
</ul>
</div>
{{ for $apps as $app }}
<div class='oauthapp'>
<img src='$app.icon' class="{{ if $app.icon }} {{ else }}noicon{{ endif }}">
{{ if $app.name }}<h4>$app.name</h4>{{ else }}<h4>$noname</h4>{{ endif }}
{{ if $app.my }}
{{ if $app.oauth_token }}
<div class="settings-submit-wrapper" ><button class="settings-submit" type="submit" name="remove" value="$app.oauth_token">$remove</button></div>
{{ endif }}
{{ endif }}
{{ if $app.my }}
<a href="$baseurl/settings/oauth/edit/$app.client_id" class="icon edit" title="$edit">&nbsp;</a>
<a href="$baseurl/settings/oauth/delete/$app.client_id" class="icon drop" title="$delete">&nbsp;</a>
{{ endif }}
</div>
{{ endfor }}
</form>

View file

@ -0,0 +1,17 @@
$tabs
<h1>$title</h1>
<form method="POST">
{{ inc field_input.tpl with $field=$name }}{{ endinc }}
{{ inc field_input.tpl with $field=$key }}{{ endinc }}
{{ inc field_input.tpl with $field=$secret }}{{ endinc }}
{{ inc field_input.tpl with $field=$redirect }}{{ endinc }}
{{ inc field_input.tpl with $field=$icon }}{{ endinc }}
<div class="settings-submit-wrapper" >
<input type="submit" name="submit" class="settings-submit" value="$submit" />
<input type="submit" name="cancel" class="settings-submit" value="$cancel" />
</div>
</form>

View file

@ -2779,6 +2779,28 @@ a.mail-list-link {
.panel_text .progress { width: 50%; overflow: hidden; height: auto; border: 1px solid #cccccc; margin-bottom: 5px}
.panel_text .progress span {float: right; display: block; width: 25%; background-color: #eeeeee; text-align: right;}
/**
* OAuth
*/
.oauthapp {
height: auto; overflow: auto;
border-bottom: 2px solid #cccccc;
padding-bottom: 1em;
margin-bottom: 1em;
}
.oauthapp img {
float: left;
width: 48px; height: 48px;
margin: 10px;
}
.oauthapp img.noicon {
background-image: url("../../../images/icons/48/plugin.png");
background-position: center center;
background-repeat: no-repeat;
}
.oauthapp a {
float: left;
}
/**
* ICONS