From 585d283ff9365d8c9db5c6ad4a023781404e9f37 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 9 May 2021 22:23:21 +0000 Subject: [PATCH] Preparation for Oauth client registration --- database.sql | 17 +++++- src/DI.php | 8 +++ src/Factory/Api/Mastodon/Application.php | 49 +++++++++++++++++ src/Module/Api/Mastodon/Apps.php | 68 ++++++++++++++++++++++++ src/Object/Api/Mastodon/Application.php | 42 ++++++++++++++- static/dbstructure.config.php | 18 ++++++- static/routes.config.php | 2 +- 7 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 src/Factory/Api/Mastodon/Application.php create mode 100644 src/Module/Api/Mastodon/Apps.php diff --git a/database.sql b/database.sql index 7a18fad5cb..666a48f667 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2021.06-dev (Siberian Iris) --- DB_UPDATE_VERSION 1414 +-- DB_UPDATE_VERSION 1415 -- ------------------------------------------ @@ -364,6 +364,21 @@ CREATE TABLE IF NOT EXISTS `apcontact` ( FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='ActivityPub compatible contacts - used in the ActivityPub implementation'; +-- +-- TABLE application +-- +CREATE TABLE IF NOT EXISTS `application` ( + `id` int unsigned NOT NULL auto_increment COMMENT 'generated index', + `client_id` varchar(64) NOT NULL COMMENT '', + `client_secret` varchar(64) NOT NULL COMMENT '', + `name` varchar(255) NOT NULL COMMENT '', + `redirect_uri` varchar(255) NOT NULL COMMENT '', + `website` varchar(255) COMMENT '', + `scopes` varchar(255) COMMENT '', + PRIMARY KEY(`id`), + UNIQUE INDEX `client_id` (`client_id`) +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth application'; + -- -- TABLE attach -- diff --git a/src/DI.php b/src/DI.php index 987cc4fbed..37091a5ab8 100644 --- a/src/DI.php +++ b/src/DI.php @@ -239,6 +239,14 @@ abstract class DI return self::$dice->create(Factory\Api\Mastodon\Account::class); } + /** + * @return Factory\Api\Mastodon\Application + */ + public static function mstdnApplication() + { + return self::$dice->create(Factory\Api\Mastodon\Application::class); + } + /** * @return Factory\Api\Mastodon\Attachment */ diff --git a/src/Factory/Api/Mastodon/Application.php b/src/Factory/Api/Mastodon/Application.php new file mode 100644 index 0000000000..b45f4e9f4c --- /dev/null +++ b/src/Factory/Api/Mastodon/Application.php @@ -0,0 +1,49 @@ +. + * + */ + +namespace Friendica\Factory\Api\Mastodon; + +use Friendica\BaseFactory; +use Friendica\Database\DBA; + +class Application extends BaseFactory +{ + /** + * @param int $id Application ID + */ + public function createFromApplicationId(int $id) + { + $application = DBA::selectFirst('application', ['client_id', 'client_secret', 'id', 'name', 'redirect_uri', 'website'], ['id' => $id]); + if (!DBA::isResult($application)) { + return []; + } + + $object = new \Friendica\Object\Api\Mastodon\Application( + $application['name'], + $application['client_id'], + $application['client_secret'], + $application['id'], + $application['redirect_uri'], + $application['website']); + + return $object->toArray(); + } +} diff --git a/src/Module/Api/Mastodon/Apps.php b/src/Module/Api/Mastodon/Apps.php new file mode 100644 index 0000000000..36bf4ba868 --- /dev/null +++ b/src/Module/Api/Mastodon/Apps.php @@ -0,0 +1,68 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Module\BaseApi; + +/** + * Apps class to register new OAuth clients + */ +class Apps extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function post(array $parameters = []) + { + $name = !isset($_REQUEST['client_name']) ? '' : $_REQUEST['client_name']; + $redirect = !isset($_REQUEST['redirect_uris']) ? '' : $_REQUEST['redirect_uris']; + $scopes = !isset($_REQUEST['scopes']) ? '' : $_REQUEST['scopes']; + $website = !isset($_REQUEST['website']) ? '' : $_REQUEST['website']; + + if (empty($name) || empty($redirect)) { + DI::mstdnError()->RecordNotFound(); + } + + $client_id = base64_encode(openssl_random_pseudo_bytes(32)); + $client_secret = bin2hex(random_bytes(32)); + + $fields = ['client_id' => $client_id, 'client_secret' => $client_secret, 'name' => $name, 'redirect_uri' => $redirect]; + + if (!empty($scopes)) { + $fields['scopes'] = $scopes; + } + + if (!empty($website)) { + $fields['website'] = $website; + } + + if (!DBA::insert('application', $fields)) { + DI::mstdnError()->RecordNotFound(); + } + + System::jsonExit(DI::mstdnApplication()->createFromApplicationId(DBA::lastInsertId())); + } +} diff --git a/src/Object/Api/Mastodon/Application.php b/src/Object/Api/Mastodon/Application.php index c8bb03a30d..1f31ca8ec7 100644 --- a/src/Object/Api/Mastodon/Application.php +++ b/src/Object/Api/Mastodon/Application.php @@ -30,8 +30,18 @@ use Friendica\BaseDataTransferObject; */ class Application extends BaseDataTransferObject { + /** @var string */ + protected $client_id; + /** @var string */ + protected $client_secret; + /** @var int */ + protected $id; /** @var string */ protected $name; + /** @var string */ + protected $redirect_uri; + /** @var string */ + protected $website; /** * Creates an application entry @@ -39,8 +49,36 @@ class Application extends BaseDataTransferObject * @param array $item * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(string $name) + public function __construct(string $name, string $client_id = null, string $client_secret = null, int $id = null, string $redirect_uri = null, string $website = null) { - $this->name = $name; + $this->client_id = $client_id; + $this->client_secret = $client_secret; + $this->id = $id; + $this->name = $name; + $this->redirect_uri = $redirect_uri; + $this->website = $website; + } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray(): array + { + $application = parent::toArray(); + + if (empty($application['id'])) { + unset($application['client_id']); + unset($application['client_secret']); + unset($application['id']); + unset($application['redirect_uri']); + } + + if (empty($application['website'])) { + unset($application['website']); + } + + return $application; } } diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 247552761f..052f73b9cf 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1414); + define('DB_UPDATE_VERSION', 1415); } return [ @@ -426,6 +426,22 @@ return [ "gsid" => ["gsid"] ] ], + "application" => [ + "comment" => "OAuth application", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "generated index"], + "client_id" => ["type" => "varchar(64)", "not null" => "1", "comment" => ""], + "client_secret" => ["type" => "varchar(64)", "not null" => "1", "comment" => ""], + "name" => ["type" => "varchar(255)", "not null" => "1", "comment" => ""], + "redirect_uri" => ["type" => "varchar(255)", "not null" => "1", "comment" => ""], + "website" => ["type" => "varchar(255)", "comment" => ""], + "scopes" => ["type" => "varchar(255)", "comment" => ""], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "client_id" => ["UNIQUE", "client_id"] + ] + ], "attach" => [ "comment" => "file attachments", "fields" => [ diff --git a/static/routes.config.php b/static/routes.config.php index a21ff336da..5fbcffbd7c 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -86,7 +86,7 @@ return [ '/announcements' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented '/announcements/{id:\d+}/dismiss' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented '/announcements/{id:\d+}/reactions/{name}' => [Module\Api\Mastodon\Unimplemented::class, [R::PUT, R::DELETE]], // not implemented - '/apps' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], + '/apps' => [Module\Api\Mastodon\Apps::class, [ R::POST]], '/apps/verify_credentials' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], '/blocks' => [Module\Api\Mastodon\Blocks::class, [R::GET ]], '/bookmarks' => [Module\Api\Mastodon\Bookmarks::class, [R::GET ]],