The legacy OAuth server is removed

This commit is contained in:
Michael 2021-07-20 17:41:04 +00:00
parent 110f8018eb
commit 6668066099
16 changed files with 347 additions and 1314 deletions

View File

@ -231,21 +231,6 @@ CREATE TABLE IF NOT EXISTS `tag` (
INDEX `url` (`url`) INDEX `url` (`url`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='tags and mentions'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='tags and mentions';
--
-- TABLE clients
--
CREATE TABLE IF NOT EXISTS `clients` (
`client_id` varchar(20) NOT NULL COMMENT '',
`pw` varchar(20) NOT NULL DEFAULT '' COMMENT '',
`redirect_uri` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`name` text COMMENT '',
`icon` text COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`client_id`),
INDEX `uid` (`uid`),
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
-- --
-- TABLE permissionset -- TABLE permissionset
-- --
@ -434,20 +419,6 @@ CREATE TABLE IF NOT EXISTS `attach` (
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='file attachments'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='file attachments';
--
-- TABLE auth_codes
--
CREATE TABLE IF NOT EXISTS `auth_codes` (
`id` varchar(40) NOT NULL COMMENT '',
`client_id` varchar(20) NOT NULL DEFAULT '' COMMENT '',
`redirect_uri` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`expires` int NOT NULL DEFAULT 0 COMMENT '',
`scope` varchar(250) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY(`id`),
INDEX `client_id` (`client_id`),
FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
-- --
-- TABLE cache -- TABLE cache
-- --
@ -1486,23 +1457,6 @@ CREATE TABLE IF NOT EXISTS `storage` (
PRIMARY KEY(`id`) PRIMARY KEY(`id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Data stored by Database storage backend'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Data stored by Database storage backend';
--
-- TABLE tokens
--
CREATE TABLE IF NOT EXISTS `tokens` (
`id` varchar(40) NOT NULL COMMENT '',
`secret` text COMMENT '',
`client_id` varchar(20) NOT NULL DEFAULT '',
`expires` int NOT NULL DEFAULT 0 COMMENT '',
`scope` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`id`),
INDEX `client_id` (`client_id`),
INDEX `uid` (`uid`),
FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
-- --
-- TABLE userd -- TABLE userd
-- --

View File

@ -13,9 +13,7 @@ Database Tables
| [application](help/database/db_application) | OAuth application | | [application](help/database/db_application) | OAuth application |
| [application-token](help/database/db_application-token) | OAuth user token | | [application-token](help/database/db_application-token) | OAuth user token |
| [attach](help/database/db_attach) | file attachments | | [attach](help/database/db_attach) | file attachments |
| [auth_codes](help/database/db_auth_codes) | OAuth usage |
| [cache](help/database/db_cache) | Stores temporary data | | [cache](help/database/db_cache) | Stores temporary data |
| [clients](help/database/db_clients) | OAuth usage |
| [config](help/database/db_config) | main configuration storage | | [config](help/database/db_config) | main configuration storage |
| [contact](help/database/db_contact) | contact table | | [contact](help/database/db_contact) | contact table |
| [contact-relation](help/database/db_contact-relation) | Contact relations | | [contact-relation](help/database/db_contact-relation) | Contact relations |
@ -69,7 +67,6 @@ Database Tables
| [session](help/database/db_session) | web session storage | | [session](help/database/db_session) | web session storage |
| [storage](help/database/db_storage) | Data stored by Database storage backend | | [storage](help/database/db_storage) | Data stored by Database storage backend |
| [tag](help/database/db_tag) | tags and mentions | | [tag](help/database/db_tag) | tags and mentions |
| [tokens](help/database/db_tokens) | OAuth usage |
| [user](help/database/db_user) | The local users | | [user](help/database/db_user) | The local users |
| [user-contact](help/database/db_user-contact) | User specific public contact data | | [user-contact](help/database/db_user-contact) | User specific public contact data |
| [userd](help/database/db_userd) | Deleted usernames | | [userd](help/database/db_userd) | Deleted usernames |

View File

@ -1,32 +0,0 @@
Table auth_codes
===========
OAuth usage
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------ | ----------- | ------------ | ---- | --- | ------- | ----- |
| id | | varchar(40) | NO | PRI | NULL | |
| client_id | | varchar(20) | NO | | | |
| redirect_uri | | varchar(200) | NO | | | |
| expires | | int | NO | | 0 | |
| scope | | varchar(250) | NO | | | |
Indexes
------------
| Name | Fields |
| --------- | --------- |
| PRIMARY | id |
| client_id | client_id |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| client_id | [clients](help/database/db_clients) | client_id |
Return to [database documentation](help/database)

View File

@ -1,33 +0,0 @@
Table clients
===========
OAuth usage
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------ | ----------- | ------------------ | ---- | --- | ------- | ----- |
| client_id | | varchar(20) | NO | PRI | NULL | |
| pw | | varchar(20) | NO | | | |
| redirect_uri | | varchar(200) | NO | | | |
| name | | text | YES | | NULL | |
| icon | | text | YES | | NULL | |
| uid | User id | mediumint unsigned | NO | | 0 | |
Indexes
------------
| Name | Fields |
| ------- | --------- |
| PRIMARY | client_id |
| uid | uid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uid | [user](help/database/db_user) | uid |
Return to [database documentation](help/database)

View File

@ -1,35 +0,0 @@
Table tokens
===========
OAuth usage
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| --------- | ----------- | ------------------ | ---- | --- | ------- | ----- |
| id | | varchar(40) | NO | PRI | NULL | |
| secret | | text | YES | | NULL | |
| client_id | | varchar(20) | NO | | | |
| expires | | int | NO | | 0 | |
| scope | | varchar(200) | NO | | | |
| uid | User id | mediumint unsigned | NO | | 0 | |
Indexes
------------
| Name | Fields |
| --------- | --------- |
| PRIMARY | id |
| client_id | client_id |
| uid | uid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| client_id | [clients](help/database/db_clients) | client_id |
| uid | [user](help/database/db_user) | uid |
Return to [database documentation](help/database)

View File

@ -57,10 +57,7 @@ use Friendica\Network\HTTPException\UnauthorizedException;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Security\FKOAuth1;
use Friendica\Security\OAuth; use Friendica\Security\OAuth;
use Friendica\Security\OAuth1\OAuthRequest;
use Friendica\Security\OAuth1\OAuthUtil;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images; use Friendica\Util\Images;
use Friendica\Util\Network; use Friendica\Util\Network;
@ -206,24 +203,6 @@ function api_login(App $a)
} }
if (empty($_SERVER['PHP_AUTH_USER'])) { if (empty($_SERVER['PHP_AUTH_USER'])) {
// Try OAuth when no user is provided
$oauth1 = new FKOAuth1();
// login with oauth
try {
$request = OAuthRequest::from_request();
list($consumer, $token) = $oauth1->verify_request($request);
if (!is_null($token)) {
$oauth1->loginUser($token->uid);
Session::set('allow_api', true);
return;
}
echo __FILE__.__LINE__.__FUNCTION__ . "<pre>";
var_dump($consumer, $token);
die();
} catch (Exception $e) {
Logger::warning(API_LOG_PREFIX . 'OAuth error', ['module' => 'api', 'action' => 'login', 'exception' => $e->getMessage()]);
}
Logger::debug(API_LOG_PREFIX . 'failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]); Logger::debug(API_LOG_PREFIX . 'failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
header('WWW-Authenticate: Basic realm="Friendica"'); header('WWW-Authenticate: Basic realm="Friendica"');
throw new UnauthorizedException("This API requires login"); throw new UnauthorizedException("This API requires login");
@ -4057,48 +4036,6 @@ api_register_func('api/direct_messages/all', 'api_direct_messages_all', true);
api_register_func('api/direct_messages/sent', 'api_direct_messages_sentbox', true); api_register_func('api/direct_messages/sent', 'api_direct_messages_sentbox', true);
api_register_func('api/direct_messages', 'api_direct_messages_inbox', true); api_register_func('api/direct_messages', 'api_direct_messages_inbox', true);
/**
* Returns an OAuth Request Token.
*
* @see https://oauth.net/core/1.0/#auth_step1
*/
function api_oauth_request_token()
{
$oauth1 = new FKOAuth1();
try {
$r = $oauth1->fetch_request_token(OAuthRequest::from_request());
} catch (Exception $e) {
echo "error=" . OAuthUtil::urlencode_rfc3986($e->getMessage());
exit();
}
echo $r;
exit();
}
/**
* Returns an OAuth Access Token.
*
* @return array|string
* @see https://oauth.net/core/1.0/#auth_step3
*/
function api_oauth_access_token()
{
$oauth1 = new FKOAuth1();
try {
$r = $oauth1->fetch_access_token(OAuthRequest::from_request());
} catch (Exception $e) {
echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage());
exit();
}
echo $r;
exit();
}
/// @TODO move to top of file or somewhere better
api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
/** /**
* delete a complete photoalbum with all containing photos from database through api * delete a complete photoalbum with all containing photos from database through api
* *

View File

@ -20,32 +20,10 @@
*/ */
use Friendica\App; use Friendica\App;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Module\Security\Login;
use Friendica\Security\OAuth1\OAuthRequest;
use Friendica\Security\OAuth1\OAuthUtil;
require_once __DIR__ . '/../include/api.php'; require_once __DIR__ . '/../include/api.php';
function oauth_get_client(OAuthRequest $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'", DBA::escape($token));
if (!DBA::isResult($r)) {
return null;
}
return $r[0];
}
function api_post(App $a) function api_post(App $a)
{ {
if (!local_user()) { if (!local_user()) {
@ -61,76 +39,6 @@ function api_post(App $a)
function api_content(App $a) function api_content(App $a)
{ {
if (DI::args()->getCommand() == 'api/oauth/authorize') {
/*
* api/oauth/authorize interact with the user. return a standard page
*/
DI::page()['template'] = "minimal";
// get consumer/client from request token
try {
$request = OAuthRequest::from_request();
} catch (Exception $e) {
echo "<pre>";
var_dump($e);
exit();
}
if (!empty($_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());
DI::config()->set("oauth", $verifier, local_user());
if ($consumer->callback_url != null) {
$params = $request->get_parameters();
$glue = "?";
if (strstr($consumer->callback_url, $glue)) {
$glue = "?";
}
DI::baseUrl()->redirect($consumer->callback_url . $glue . 'oauth_token=' . OAuthUtil::urlencode_rfc3986($params['oauth_token']) . '&oauth_verifier=' . OAuthUtil::urlencode_rfc3986($verifier));
exit();
}
$tpl = Renderer::getMarkupTemplate("oauth_authorize_done.tpl");
$o = Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->t('Authorize application connection'),
'$info' => DI::l10n()->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(DI::l10n()->t('Please login to continue.'));
return Login::form(DI::args()->getQueryString(), false, $request->get_parameters());
}
//FKOAuth1::loginUser(4);
$app = oauth_get_client($request);
if (is_null($app)) {
return "Invalid request. Unknown token.";
}
$tpl = Renderer::getMarkupTemplate('oauth_authorize.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->t('Authorize application connection'),
'$app' => $app,
'$authorize' => DI::l10n()->t('Do you want to authorize this application to access your posts and contacts, and/or create new posts for you?'),
'$yes' => DI::l10n()->t('Yes'),
'$no' => DI::l10n()->t('No'),
]);
return $o;
}
echo api_call($a); echo api_call($a);
exit(); exit();
} }

View File

@ -66,63 +66,6 @@ function settings_post(App $a)
return; return;
} }
$old_page_flags = $a->user['page-flags'];
if (($a->argc > 1) && ($a->argv[1] === 'oauth') && !empty($_POST['remove'])) {
BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth');
$key = $_POST['remove'];
DBA::delete('tokens', ['id' => $key, 'uid' => local_user()]);
DI::baseUrl()->redirect('settings/oauth/', true);
return;
}
if (($a->argc > 2) && ($a->argv[1] === 'oauth') && ($a->argv[2] === 'edit'||($a->argv[2] === 'add')) && !empty($_POST['submit'])) {
BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth');
$name = $_POST['name'] ?? '';
$key = $_POST['key'] ?? '';
$secret = $_POST['secret'] ?? '';
$redirect = $_POST['redirect'] ?? '';
$icon = $_POST['icon'] ?? '';
if ($name == "" || $key == "" || $secret == "") {
notice(DI::l10n()->t("Missing some important data!"));
} else {
if ($_POST['submit'] == DI::l10n()->t("Update")) {
q("UPDATE clients SET
client_id='%s',
pw='%s',
name='%s',
redirect_uri='%s',
icon='%s',
uid=%d
WHERE client_id='%s'",
DBA::escape($key),
DBA::escape($secret),
DBA::escape($name),
DBA::escape($redirect),
DBA::escape($icon),
local_user(),
DBA::escape($key)
);
} else {
q("INSERT INTO clients
(client_id, pw, name, redirect_uri, icon, uid)
VALUES ('%s', '%s', '%s', '%s', '%s',%d)",
DBA::escape($key),
DBA::escape($secret),
DBA::escape($name),
DBA::escape($redirect),
DBA::escape($icon),
local_user()
);
}
}
DI::baseUrl()->redirect('settings/oauth/', true);
return;
}
if (($a->argc > 1) && ($a->argv[1] == 'addon')) { if (($a->argc > 1) && ($a->argv[1] == 'addon')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/settings/addon', 'settings_addon'); BaseModule::checkFormSecurityTokenRedirectOnError('/settings/addon', 'settings_addon');

View File

@ -81,7 +81,8 @@ class DBStructure
$old_tables = ['fserver', 'gcign', 'gcontact', 'gcontact-relation', 'gfollower' ,'glink', 'item-delivery-data', $old_tables = ['fserver', 'gcign', 'gcontact', 'gcontact-relation', 'gfollower' ,'glink', 'item-delivery-data',
'item-activity', 'item-content', 'item_id', 'participation', 'poll', 'poll_result', 'queue', 'retriever_rule', 'item-activity', 'item-content', 'item_id', 'participation', 'poll', 'poll_result', 'queue', 'retriever_rule',
'deliverq', 'dsprphotoq', 'ffinder', 'sign', 'spam', 'term', 'user-item', 'thread', 'item', 'challenge']; 'deliverq', 'dsprphotoq', 'ffinder', 'sign', 'spam', 'term', 'user-item', 'thread', 'item', 'challenge',
'auth_codes', 'clients', 'tokens'];
$tables = DBA::selectToArray(['INFORMATION_SCHEMA' => 'TABLES'], ['TABLE_NAME'], $tables = DBA::selectToArray(['INFORMATION_SCHEMA' => 'TABLES'], ['TABLE_NAME'],
['TABLE_SCHEMA' => DBA::databaseName(), 'TABLE_TYPE' => 'BASE TABLE']); ['TABLE_SCHEMA' => DBA::databaseName(), 'TABLE_TYPE' => 'BASE TABLE']);
@ -1369,7 +1370,7 @@ class DBStructure
echo "permissionset: Table not found\n"; echo "permissionset: Table not found\n";
} }
if (!self::existsForeignKeyForField('tokens', 'client_id')) { if (self::existsTable('tokens') && self::existsTable('clients') && !self::existsForeignKeyForField('tokens', 'client_id')) {
$tokens = DBA::p("SELECT `tokens`.`id` FROM `tokens` $tokens = DBA::p("SELECT `tokens`.`id` FROM `tokens`
LEFT JOIN `clients` ON `clients`.`client_id` = `tokens`.`client_id` LEFT JOIN `clients` ON `clients`.`client_id` = `tokens`.`client_id`
WHERE `clients`.`client_id` IS NULL"); WHERE `clients`.`client_id` IS NULL");

View File

@ -1,66 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Security;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Security\OAuth1\OAuthServer;
use Friendica\Security\OAuth1\Signature\OAuthSignatureMethod_HMAC_SHA1;
use Friendica\Security\OAuth1\Signature\OAuthSignatureMethod_PLAINTEXT;
/**
* OAuth protocol
*/
class FKOAuth1 extends OAuthServer
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct(new FKOAuthDataStore());
$this->add_signature_method(new OAuthSignatureMethod_PLAINTEXT());
$this->add_signature_method(new OAuthSignatureMethod_HMAC_SHA1());
}
/**
* @param string $uid user id
* @return void
* @throws HTTPException\ForbiddenException
* @throws HTTPException\InternalServerErrorException
*/
public function loginUser($uid)
{
Logger::notice("FKOAuth1::loginUser $uid");
$a = DI::app();
$record = DBA::selectFirst('user', [], ['uid' => $uid, 'blocked' => 0, 'account_expired' => 0, 'account_removed' => 0, 'verified' => 1]);
if (!DBA::isResult($record) || empty($uid)) {
Logger::info('FKOAuth1::loginUser failure', ['server' => $_SERVER]);
header('HTTP/1.0 401 Unauthorized');
die('This api requires login');
}
DI::auth()->setForUser($a, $record, true);
}
}

View File

@ -1,197 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Security;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\Strings;
use Friendica\Security\OAuth1\OAuthConsumer;
use Friendica\Security\OAuth1\OAuthDataStore;
use Friendica\Security\OAuth1\OAuthToken;
define('REQUEST_TOKEN_DURATION', 300);
define('ACCESS_TOKEN_DURATION', 31536000);
/**
* Friendica\Security\OAuth1\OAuthDataStore class
*/
class FKOAuthDataStore extends OAuthDataStore
{
/**
* @return string
* @throws \Exception
*/
private static function genToken()
{
return Strings::getRandomHex(32);
}
/**
* @param string $consumer_key key
* @return OAuthConsumer|null
* @throws \Exception
*/
public function lookup_consumer($consumer_key)
{
Logger::log(__function__ . ":" . $consumer_key);
$s = DBA::select('clients', ['client_id', 'pw', 'redirect_uri'], ['client_id' => $consumer_key]);
$r = DBA::toArray($s);
if (DBA::isResult($r)) {
return new OAuthConsumer($r[0]['client_id'], $r[0]['pw'], $r[0]['redirect_uri']);
}
return null;
}
/**
* @param OAuthConsumer $consumer
* @param string $token_type
* @param string $token_id
* @return OAuthToken|null
* @throws \Exception
*/
public function lookup_token(OAuthConsumer $consumer, $token_type, $token_id)
{
Logger::log(__function__ . ":" . $consumer . ", " . $token_type . ", " . $token_id);
$s = DBA::select('tokens', ['id', 'secret', 'scope', 'expires', 'uid'], ['client_id' => $consumer->key, 'scope' => $token_type, 'id' => $token_id]);
$r = DBA::toArray($s);
if (DBA::isResult($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;
}
/**
* @param OAuthConsumer $consumer
* @param OAuthToken $token
* @param string $nonce
* @param int $timestamp
* @return mixed
* @throws \Exception
*/
public function lookup_nonce(OAuthConsumer $consumer, OAuthToken $token, $nonce, int $timestamp)
{
$token = DBA::selectFirst('tokens', ['id', 'secret'], ['client_id' => $consumer->key, 'id' => $nonce, 'expires' => $timestamp]);
if (DBA::isResult($token)) {
return new OAuthToken($token['id'], $token['secret']);
}
return null;
}
/**
* @param OAuthConsumer $consumer
* @param string $callback
* @return OAuthToken|null
* @throws \Exception
*/
public function new_request_token(OAuthConsumer $consumer, $callback = null)
{
Logger::log(__function__ . ":" . $consumer . ", " . $callback);
$key = self::genToken();
$sec = self::genToken();
if ($consumer->key) {
$k = $consumer->key;
} else {
$k = $consumer;
}
$r = DBA::insert(
'tokens',
[
'id' => $key,
'secret' => $sec,
'client_id' => $k,
'scope' => 'request',
'expires' => time() + REQUEST_TOKEN_DURATION
]
);
if (!$r) {
return null;
}
return new OAuthToken($key, $sec);
}
/**
* @param OAuthToken $token token
* @param OAuthConsumer $consumer consumer
* @param string $verifier optional, defult null
* @return OAuthToken
* @throws \Exception
*/
public function new_access_token(OAuthToken $token, OAuthConsumer $consumer, $verifier = null)
{
Logger::log(__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 = DI::config()->get("oauth", $verifier);
Logger::log(__function__ . ":" . $verifier . "," . $uverifier);
if (is_null($verifier) || ($uverifier !== false)) {
$key = self::genToken();
$sec = self::genToken();
$r = DBA::insert(
'tokens',
[
'id' => $key,
'secret' => $sec,
'client_id' => $consumer->key,
'scope' => 'access',
'expires' => time() + ACCESS_TOKEN_DURATION,
'uid' => $uverifier
]
);
if ($r) {
$ret = new OAuthToken($key, $sec);
}
}
DBA::delete('tokens', ['id' => $token->key]);
if (!is_null($ret) && !is_null($uverifier)) {
DI::config()->delete("oauth", $verifier);
}
return $ret;
}
}

View File

@ -1,290 +0,0 @@
<?php
namespace Friendica\Security\OAuth1;
use Friendica\Security\FKOAuthDataStore;
use Friendica\Security\OAuth1\Signature;
class OAuthServer
{
protected $timestamp_threshold = 300; // in seconds, five minutes
protected $version = '1.0'; // hi blaine
/** @var Signature\OAuthSignatureMethod[] */
protected $signature_methods = [];
/** @var FKOAuthDataStore */
protected $data_store;
function __construct(FKOAuthDataStore $data_store)
{
$this->data_store = $data_store;
}
public function add_signature_method(Signature\OAuthSignatureMethod $signature_method)
{
$this->signature_methods[$signature_method->get_name()] =
$signature_method;
}
// high level functions
/**
* process a request_token request
* returns the request token on success
*
* @param OAuthRequest $request
*
* @return OAuthToken|null
* @throws OAuthException
*/
public function fetch_request_token(OAuthRequest $request)
{
$this->get_version($request);
$consumer = $this->get_consumer($request);
// no token required for the initial token request
$token = null;
$this->check_signature($request, $consumer, $token);
// Rev A change
$callback = $request->get_parameter('oauth_callback');
$new_token = $this->data_store->new_request_token($consumer, $callback);
return $new_token;
}
/**
* process an access_token request
* returns the access token on success
*
* @param OAuthRequest $request
*
* @return object
* @throws OAuthException
*/
public function fetch_access_token(OAuthRequest $request)
{
$this->get_version($request);
$consumer = $this->get_consumer($request);
// requires authorized request token
$token = $this->get_token($request, $consumer, "request");
$this->check_signature($request, $consumer, $token);
// Rev A change
$verifier = $request->get_parameter('oauth_verifier');
$new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
return $new_token;
}
/**
* verify an api call, checks all the parameters
*
* @param OAuthRequest $request
*
* @return array
* @throws OAuthException
*/
public function verify_request(OAuthRequest $request)
{
$this->get_version($request);
$consumer = $this->get_consumer($request);
$token = $this->get_token($request, $consumer, "access");
$this->check_signature($request, $consumer, $token);
return [$consumer, $token];
}
// Internals from here
/**
* version 1
*
* @param OAuthRequest $request
*
* @return string
* @throws OAuthException
*/
private function get_version(OAuthRequest $request)
{
$version = $request->get_parameter("oauth_version");
if (!$version) {
// Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
// Chapter 7.0 ("Accessing Protected Ressources")
$version = '1.0';
}
if ($version !== $this->version) {
throw new OAuthException("OAuth version '$version' not supported");
}
return $version;
}
/**
* figure out the signature with some defaults
*
* @param OAuthRequest $request
*
* @return Signature\OAuthSignatureMethod
* @throws OAuthException
*/
private function get_signature_method(OAuthRequest $request)
{
$signature_method =
@$request->get_parameter("oauth_signature_method");
if (!$signature_method) {
// According to chapter 7 ("Accessing Protected Ressources") the signature-method
// parameter is required, and we can't just fallback to PLAINTEXT
throw new OAuthException('No signature method parameter. This parameter is required');
}
if (!in_array(
$signature_method,
array_keys($this->signature_methods)
)) {
throw new OAuthException(
"Signature method '$signature_method' not supported " .
"try one of the following: " .
implode(", ", array_keys($this->signature_methods))
);
}
return $this->signature_methods[$signature_method];
}
/**
* try to find the consumer for the provided request's consumer key
*
* @param OAuthRequest $request
*
* @return OAuthConsumer
* @throws OAuthException
*/
private function get_consumer(OAuthRequest $request)
{
$consumer_key = @$request->get_parameter("oauth_consumer_key");
if (!$consumer_key) {
throw new OAuthException("Invalid consumer key");
}
$consumer = $this->data_store->lookup_consumer($consumer_key);
if (!$consumer) {
throw new OAuthException("Invalid consumer");
}
return $consumer;
}
/**
* try to find the token for the provided request's token key
*
* @param OAuthRequest $request
* @param $consumer
* @param string $token_type
*
* @return OAuthToken|null
* @throws OAuthException
*/
private function get_token(OAuthRequest &$request, $consumer, $token_type = "access")
{
$token_field = @$request->get_parameter('oauth_token');
$token = $this->data_store->lookup_token(
$consumer,
$token_type,
$token_field
);
if (!$token) {
throw new OAuthException("Invalid $token_type token: $token_field");
}
return $token;
}
/**
* all-in-one function to check the signature on a request
* should guess the signature method appropriately
*
* @param OAuthRequest $request
* @param OAuthConsumer $consumer
* @param OAuthToken|null $token
*
* @throws OAuthException
*/
private function check_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token = null)
{
// this should probably be in a different method
$timestamp = @$request->get_parameter('oauth_timestamp');
$nonce = @$request->get_parameter('oauth_nonce');
$this->check_timestamp($timestamp);
$this->check_nonce($consumer, $token, $nonce, $timestamp);
$signature_method = $this->get_signature_method($request);
$signature = $request->get_parameter('oauth_signature');
$valid_sig = $signature_method->check_signature(
$request,
$consumer,
$signature,
$token
);
if (!$valid_sig) {
throw new OAuthException("Invalid signature");
}
}
/**
* check that the timestamp is new enough
*
* @param int $timestamp
*
* @throws OAuthException
*/
private function check_timestamp($timestamp)
{
if (!$timestamp)
throw new OAuthException(
'Missing timestamp parameter. The parameter is required'
);
// verify that timestamp is recentish
$now = time();
if (abs($now - $timestamp) > $this->timestamp_threshold) {
throw new OAuthException(
"Expired timestamp, yours $timestamp, ours $now"
);
}
}
/**
* check that the nonce is not repeated
*
* @param OAuthConsumer $consumer
* @param OAuthToken $token
* @param string $nonce
* @param int $timestamp
*
* @throws OAuthException
*/
private function check_nonce(OAuthConsumer $consumer, OAuthToken $token, $nonce, int $timestamp)
{
if (!$nonce)
throw new OAuthException(
'Missing nonce parameter. The parameter is required'
);
// verify that the nonce is uniqueish
$found = $this->data_store->lookup_nonce(
$consumer,
$token,
$nonce,
$timestamp
);
if ($found) {
throw new OAuthException("Nonce already used: $nonce");
}
}
}

View File

@ -40,14 +40,12 @@ class OptimizeTables
Logger::info('Optimize start'); Logger::info('Optimize start');
DBA::e("OPTIMIZE TABLE `auth_codes`");
DBA::e("OPTIMIZE TABLE `cache`"); DBA::e("OPTIMIZE TABLE `cache`");
DBA::e("OPTIMIZE TABLE `locks`"); DBA::e("OPTIMIZE TABLE `locks`");
DBA::e("OPTIMIZE TABLE `oembed`"); DBA::e("OPTIMIZE TABLE `oembed`");
DBA::e("OPTIMIZE TABLE `parsed_url`"); DBA::e("OPTIMIZE TABLE `parsed_url`");
DBA::e("OPTIMIZE TABLE `profile_check`"); DBA::e("OPTIMIZE TABLE `profile_check`");
DBA::e("OPTIMIZE TABLE `session`"); DBA::e("OPTIMIZE TABLE `session`");
DBA::e("OPTIMIZE TABLE `tokens`");
Logger::info('Optimize end'); Logger::info('Optimize end');

View File

@ -289,21 +289,6 @@ return [
"url" => ["url"] "url" => ["url"]
] ]
], ],
"clients" => [
"comment" => "OAuth usage",
"fields" => [
"client_id" => ["type" => "varchar(20)", "not null" => "1", "primary" => "1", "comment" => ""],
"pw" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "comment" => ""],
"redirect_uri" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
"name" => ["type" => "text", "comment" => ""],
"icon" => ["type" => "text", "comment" => ""],
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "foreign" => ["user" => "uid"], "comment" => "User id"],
],
"indexes" => [
"PRIMARY" => ["client_id"],
"uid" => ["uid"],
]
],
"permissionset" => [ "permissionset" => [
"comment" => "", "comment" => "",
"fields" => [ "fields" => [
@ -494,21 +479,6 @@ return [
"uid" => ["uid"], "uid" => ["uid"],
] ]
], ],
"auth_codes" => [
"comment" => "OAuth usage",
"fields" => [
"id" => ["type" => "varchar(40)", "not null" => "1", "primary" => "1", "comment" => ""],
"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "foreign" => ["clients" => "client_id"],
"comment" => ""],
"redirect_uri" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
"expires" => ["type" => "int", "not null" => "1", "default" => "0", "comment" => ""],
"scope" => ["type" => "varchar(250)", "not null" => "1", "default" => "", "comment" => ""],
],
"indexes" => [
"PRIMARY" => ["id"],
"client_id" => ["client_id"]
]
],
"cache" => [ "cache" => [
"comment" => "Stores temporary data", "comment" => "Stores temporary data",
"fields" => [ "fields" => [
@ -1506,22 +1476,6 @@ return [
"PRIMARY" => ["id"] "PRIMARY" => ["id"]
] ]
], ],
"tokens" => [
"comment" => "OAuth usage",
"fields" => [
"id" => ["type" => "varchar(40)", "not null" => "1", "primary" => "1", "comment" => ""],
"secret" => ["type" => "text", "comment" => ""],
"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "foreign" => ["clients" => "client_id"]],
"expires" => ["type" => "int", "not null" => "1", "default" => "0", "comment" => ""],
"scope" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "foreign" => ["user" => "uid"], "comment" => "User id"],
],
"indexes" => [
"PRIMARY" => ["id"],
"client_id" => ["client_id"],
"uid" => ["uid"]
]
],
"userd" => [ "userd" => [
"comment" => "Deleted usernames", "comment" => "Deleted usernames",
"fields" => [ "fields" => [

View File

@ -240,9 +240,12 @@ function pre_update_1348()
update_1348(); update_1348();
DBA::e("DELETE FROM `auth_codes` WHERE NOT `client_id` IN (SELECT `client_id` FROM `clients`)"); if (DBStructure::existsTable('auth_codes') && DBStructure::existsTable('clients')) {
DBA::e("DELETE FROM `tokens` WHERE NOT `client_id` IN (SELECT `client_id` FROM `clients`)"); DBA::e("DELETE FROM `auth_codes` WHERE NOT `client_id` IN (SELECT `client_id` FROM `clients`)");
}
if (DBStructure::existsTable('tokens') && DBStructure::existsTable('clients')) {
DBA::e("DELETE FROM `tokens` WHERE NOT `client_id` IN (SELECT `client_id` FROM `clients`)");
}
return Update::SUCCESS; return Update::SUCCESS;
} }
@ -391,7 +394,7 @@ function pre_update_1364()
return Update::FAILED; return Update::FAILED;
} }
if (!DBA::e("DELETE FROM `clients` WHERE NOT `uid` IN (SELECT `uid` FROM `user`)")) { if (DBStructure::existsTable('clients') && !DBA::e("DELETE FROM `clients` WHERE NOT `uid` IN (SELECT `uid` FROM `user`)")) {
return Update::FAILED; return Update::FAILED;
} }
@ -463,7 +466,7 @@ function pre_update_1364()
return Update::FAILED; return Update::FAILED;
} }
if (!DBA::e("DELETE FROM `tokens` WHERE NOT `uid` IN (SELECT `uid` FROM `user`)")) { if (DBStructure::existsTable('tokens') && !DBA::e("DELETE FROM `tokens` WHERE NOT `uid` IN (SELECT `uid` FROM `user`)")) {
return Update::FAILED; return Update::FAILED;
} }

File diff suppressed because it is too large Load Diff