aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio Comuni <fabrix.xm@gmail.com>2011-11-07 17:41:23 +0100
committerFabio Comuni <fabrix.xm@gmail.com>2011-11-07 17:41:23 +0100
commit00c342e13d833fd215e5dc03a508e1abe660fe21 (patch)
treed276554274475d8a0e2d8e1e9ae34035dd64085a
parent355026ab7a53838c8e7a7b5717626ee057e5cd4c (diff)
parentb070666120fc6cbc0d90a0ad160274f8ecf7a027 (diff)
downloadvolse-hubzilla-00c342e13d833fd215e5dc03a508e1abe660fe21.tar.gz
volse-hubzilla-00c342e13d833fd215e5dc03a508e1abe660fe21.tar.bz2
volse-hubzilla-00c342e13d833fd215e5dc03a508e1abe660fe21.zip
Merge branch 'oauthapi'
-rw-r--r--boot.php3
-rw-r--r--images/icons/10/plugin.pngbin0 -> 471 bytes
-rw-r--r--images/icons/16/plugin.pngbin0 -> 649 bytes
-rw-r--r--images/icons/22/plugin.pngbin0 -> 844 bytes
-rw-r--r--images/icons/48/plugin.pngbin0 -> 1663 bytes
-rw-r--r--images/icons/Makefile2
-rw-r--r--images/icons/plugin.pngbin0 -> 9448 bytes
-rw-r--r--include/api.php47
-rw-r--r--include/oauth.php266
-rw-r--r--library/OAuth1.php20
-rw-r--r--library/oauth2-php/CHANGELOG.txt98
-rw-r--r--library/oauth2-php/LICENSE.txt21
-rw-r--r--library/oauth2-php/lib/OAuth2.inc1560
-rw-r--r--library/oauth2-php/lib/OAuth2Client.inc721
-rw-r--r--library/oauth2-php/lib/OAuth2Exception.inc85
-rw-r--r--library/stanlemon-jgrowl-tip.tar.gzbin213973 -> 0 bytes
-rw-r--r--mod/api.php108
-rw-r--r--mod/notice.php20
-rw-r--r--mod/settings.php136
-rw-r--r--view/login.tpl5
-rw-r--r--view/minimal.php14
-rw-r--r--view/oauth_authorize.tpl10
-rw-r--r--view/oauth_authorize_done.tpl4
-rw-r--r--view/settings_oauth.tpl32
-rw-r--r--view/settings_oauth_edit.tpl17
-rw-r--r--view/theme/duepuntozero/style.css22
26 files changed, 3183 insertions, 8 deletions
diff --git a/boot.php b/boot.php
index 16107af80..cde5c195c 100644
--- a/boot.php
+++ b/boot.php
@@ -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,
diff --git a/images/icons/10/plugin.png b/images/icons/10/plugin.png
new file mode 100644
index 000000000..6cfc40786
--- /dev/null
+++ b/images/icons/10/plugin.png
Binary files differ
diff --git a/images/icons/16/plugin.png b/images/icons/16/plugin.png
new file mode 100644
index 000000000..b11d92082
--- /dev/null
+++ b/images/icons/16/plugin.png
Binary files differ
diff --git a/images/icons/22/plugin.png b/images/icons/22/plugin.png
new file mode 100644
index 000000000..cf4421125
--- /dev/null
+++ b/images/icons/22/plugin.png
Binary files differ
diff --git a/images/icons/48/plugin.png b/images/icons/48/plugin.png
new file mode 100644
index 000000000..c74c6bf46
--- /dev/null
+++ b/images/icons/48/plugin.png
Binary files differ
diff --git a/images/icons/Makefile b/images/icons/Makefile
index 6a96fb676..ec57b943e 100644
--- a/images/icons/Makefile
+++ b/images/icons/Makefile
@@ -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)) \
diff --git a/images/icons/plugin.png b/images/icons/plugin.png
new file mode 100644
index 000000000..943be0d93
--- /dev/null
+++ b/images/icons/plugin.png
Binary files differ
diff --git a/include/api.php b/include/api.php
index d94cc2942..1196e0aac 100644
--- a/include/api.php
+++ b/include/api.php
@@ -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);
+
+
diff --git a/include/oauth.php b/include/oauth.php
new file mode 100644
index 000000000..2724dcf7c
--- /dev/null
+++ b/include/oauth.php
@@ -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;
+ }
+
+}
+*/
diff --git a/library/OAuth1.php b/library/OAuth1.php
index 67a94c479..3b211b146 100644
--- a/library/OAuth1.php
+++ b/library/OAuth1.php
@@ -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");
diff --git a/library/oauth2-php/CHANGELOG.txt b/library/oauth2-php/CHANGELOG.txt
new file mode 100644
index 000000000..d854598b6
--- /dev/null
+++ b/library/oauth2-php/CHANGELOG.txt
@@ -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.
diff --git a/library/oauth2-php/LICENSE.txt b/library/oauth2-php/LICENSE.txt
new file mode 100644
index 000000000..7979b1c85
--- /dev/null
+++ b/library/oauth2-php/LICENSE.txt
@@ -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.
diff --git a/library/oauth2-php/lib/OAuth2.inc b/library/oauth2-php/lib/OAuth2.inc
new file mode 100644
index 000000000..e10e0f26d
--- /dev/null
+++ b/library/oauth2-php/lib/OAuth2.inc
@@ -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;
+ }
+}
diff --git a/library/oauth2-php/lib/OAuth2Client.inc b/library/oauth2-php/lib/OAuth2Client.inc
new file mode 100644
index 000000000..e87d723de
--- /dev/null
+++ b/library/oauth2-php/lib/OAuth2Client.inc
@@ -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);
+ }
+}
diff --git a/library/oauth2-php/lib/OAuth2Exception.inc b/library/oauth2-php/lib/OAuth2Exception.inc
new file mode 100644
index 000000000..8dc046974
--- /dev/null
+++ b/library/oauth2-php/lib/OAuth2Exception.inc
@@ -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;
+ }
+}
diff --git a/library/stanlemon-jgrowl-tip.tar.gz b/library/stanlemon-jgrowl-tip.tar.gz
deleted file mode 100644
index 07a5fb097..000000000
--- a/library/stanlemon-jgrowl-tip.tar.gz
+++ /dev/null
Binary files differ
diff --git a/mod/api.php b/mod/api.php
index fa5e43de9..ad75e6620 100644
--- a/mod/api.php
+++ b/mod/api.php
@@ -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();
}
diff --git a/mod/notice.php b/mod/notice.php
new file mode 100644
index 000000000..9d8aeed70
--- /dev/null
+++ b/mod/notice.php
@@ -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;
+
+ }
diff --git a/mod/settings.php b/mod/settings.php
index 522ae52de..938f6a0d5 100644
--- a/mod/settings.php
+++ b/mod/settings.php
@@ -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;
@@ -342,6 +394,11 @@ function settings_content(&$a) {
'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',
'sel' => ''
@@ -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 = "";
diff --git a/view/login.tpl b/view/login.tpl
index 5240bb9ad..5349fa3d8 100644
--- a/view/login.tpl
+++ b/view/login.tpl
@@ -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>
diff --git a/view/minimal.php b/view/minimal.php
new file mode 100644
index 000000000..a8c693985
--- /dev/null
+++ b/view/minimal.php
@@ -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>
+
diff --git a/view/oauth_authorize.tpl b/view/oauth_authorize.tpl
new file mode 100644
index 000000000..31f02ac50
--- /dev/null
+++ b/view/oauth_authorize.tpl
@@ -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>
diff --git a/view/oauth_authorize_done.tpl b/view/oauth_authorize_done.tpl
new file mode 100644
index 000000000..51eaea248
--- /dev/null
+++ b/view/oauth_authorize_done.tpl
@@ -0,0 +1,4 @@
+<h1>$title</h1>
+
+<p>$info</p>
+<code>$code</code>
diff --git a/view/settings_oauth.tpl b/view/settings_oauth.tpl
new file mode 100644
index 000000000..bc5866bec
--- /dev/null
+++ b/view/settings_oauth.tpl
@@ -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>
diff --git a/view/settings_oauth_edit.tpl b/view/settings_oauth_edit.tpl
new file mode 100644
index 000000000..98b7457aa
--- /dev/null
+++ b/view/settings_oauth_edit.tpl
@@ -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>
diff --git a/view/theme/duepuntozero/style.css b/view/theme/duepuntozero/style.css
index eb73bfb97..e9c0817f4 100644
--- a/view/theme/duepuntozero/style.css
+++ b/view/theme/duepuntozero/style.css
@@ -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