From dac184b281fd01b908edb6859bb84c7fee7f2a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 24 Apr 2026 12:06:58 +0200 Subject: [PATCH] abstract oauth adapters --- src/Appwrite/Auth/OAuth2.php | 7 - src/Appwrite/Auth/OAuth2/Discord.php | 4 - src/Appwrite/Auth/OAuth2/Figma.php | 4 - .../Project/Http/Project/OAuth2/Base.php | 175 ++++++++++++++++++ .../Http/Project/OAuth2/Discord/Update.php | 134 ++------------ .../Http/Project/OAuth2/Figma/Update.php | 134 ++------------ .../Http/Project/OAuth2/GitHub/Update.php | 136 ++------------ 7 files changed, 221 insertions(+), 373 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php diff --git a/src/Appwrite/Auth/OAuth2.php b/src/Appwrite/Auth/OAuth2.php index 3861004498..a8a2d175b5 100644 --- a/src/Appwrite/Auth/OAuth2.php +++ b/src/Appwrite/Auth/OAuth2.php @@ -50,13 +50,6 @@ abstract class OAuth2 $this->addScope($scope); } } - - /** - * Check if the OAuth credentials are valid - * - * @throws \Exception - */ - abstract public function verifyCredentials(): void; /** * @return string diff --git a/src/Appwrite/Auth/OAuth2/Discord.php b/src/Appwrite/Auth/OAuth2/Discord.php index ede5ce36c2..6cb682479a 100644 --- a/src/Appwrite/Auth/OAuth2/Discord.php +++ b/src/Appwrite/Auth/OAuth2/Discord.php @@ -184,8 +184,4 @@ class Discord extends OAuth2 return $this->user; } - - public function verifyCredentials(): void { - // TODO: Implement, eventuelly. Refer to GitHub.php in this directory for inspiration - } } diff --git a/src/Appwrite/Auth/OAuth2/Figma.php b/src/Appwrite/Auth/OAuth2/Figma.php index b6ce166e6b..b5e53cbed4 100644 --- a/src/Appwrite/Auth/OAuth2/Figma.php +++ b/src/Appwrite/Auth/OAuth2/Figma.php @@ -175,8 +175,4 @@ class Figma extends OAuth2 return $this->user; } - - public function verifyCredentials(): void { - // TODO: Implement, eventuelly. Refer to GitHub.php in this directory for inspiration - } } diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php new file mode 100644 index 0000000000..e2cc405a59 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php @@ -0,0 +1,175 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpPath('/v1/project/oauth2/' . $providerId) + ->desc('Update project OAuth2 ' . $providerLabel) + ->groups(['api', 'project']) + ->label('scope', 'oauth2.write') + ->label('event', 'oauth2.' . $providerId . '.update') + ->label('audits.event', 'project.oauth2.' . $providerId . '.update') + ->label('audits.resource', 'project.oauth2/{response.$id}') + ->label('sdk', new Method( + namespace: 'project', + group: 'oauth2', + name: 'updateOAuth2' . $providerLabel, + description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.', + auth: [AuthType::ADMIN, AuthType::KEY], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: static::getResponseModel(), + ) + ], + )) + ->param('clientId', null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) + ->param('clientSecret', null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) + ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->inject('response') + ->inject('dbForPlatform') + ->inject('project') + ->inject('authorization') + ->callback($this->action(...)); + } + + public function action( + ?string $clientId, + ?string $clientSecret, + ?bool $enabled, + Response $response, + Database $dbForPlatform, + Document $project, + Authorization $authorization + ): void { + $providerId = static::getProviderId(); + if(!(\in_array($providerId, \array_keys(Config::getParam('oAuthProviders'))))) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Provider ' . $providerId . ' is not supported by server configuration.'); + } + + $oAuthProviders = $project->getAttribute('oAuthProviders', []); + + $appIdKey = $providerId . 'Appid'; + $appSecretKey = $providerId . 'Secret'; + $enabledKey = $providerId . 'Enabled'; + + if (!\is_null($clientId)) { + $oAuthProviders[$appIdKey] = $clientId; + } + + if (!\is_null($clientSecret)) { + $oAuthProviders[$appSecretKey] = $clientSecret; + } + + if (!\is_null($enabled)) { + $oAuthProviders[$enabledKey] = $enabled; + } + + if($enabled === true || \is_null($enabled)) { + try { + if(empty($oAuthProviders[$appIdKey]) || empty($oAuthProviders[$appSecretKey])) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Client ID and Client Secret are required when enabling OAuth2 provider.'); + } + + $providerClass = static::getProviderClass(); + $providerInstance = new $providerClass(appId: $oAuthProviders[$appIdKey], appSecret: $oAuthProviders[$appSecretKey], callback: '', state: [], scopes: []); + + // E2E integration check + if(\method_exists($providerInstance,'verifyCredentials')) { + $providerInstance->verifyCredentials(); + } + + $oAuthProviders[$enabledKey] = true; + } catch(\Throwable $err) { + if($enabled === true) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage()); + } + } + } + + $updates = new Document([ + 'oAuthProviders' => $oAuthProviders + ]); + + $project = $authorization->skip(fn() => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); + + $response->dynamic(new Document([ + '$id' => $providerId, + 'enabled' => $oAuthProviders[$enabledKey] ?? false, + 'clientId' => $oAuthProviders[$appIdKey] ?? '', + 'clientSecret' => $oAuthProviders[$appSecretKey] ?? '', + ]), static::getResponseModel()); + } +} diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Discord/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Discord/Update.php index 091cc41637..383aee12d6 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Discord/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Discord/Update.php @@ -3,142 +3,38 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Discord; use Appwrite\Auth\OAuth2\Discord; -use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; -use Appwrite\SDK\AuthType; -use Appwrite\SDK\Method; -use Appwrite\SDK\Response as SDKResponse; +use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base; use Appwrite\Utopia\Response; -use Utopia\Config\Config; -use Utopia\Database\Database; -use Utopia\Database\Document; -use Utopia\Database\Validator\Authorization; -use Utopia\Platform\Scope\HTTP; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; -use Utopia\Validator\Text; -class Update extends Action +class Update extends Base { - use HTTP; - - public static function getName() - { - return 'updateProjectOAuth2Discord'; - } - public static function getProviderId(): string { return 'discord'; } - /** - * @return class-string - */ public static function getProviderClass(): string { return Discord::class; } - public function __construct() + public static function getProviderLabel(): string { - $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/project/oauth2/discord') - ->desc('Update project OAuth2 Discord') - ->groups(['api', 'project']) - ->label('scope', 'oauth2.write') - ->label('event', 'oauth2.discord.update') - ->label('audits.event', 'project.oauth2.discord.update') - ->label('audits.resource', 'project.oauth2/{response.$id}') - ->label('sdk', new Method( - namespace: 'project', - group: 'oauth2', - name: 'updateOAuth2Discord', - description: <<param('clientId', null, new Nullable(new Text(256, 0)), 'Client ID of Discord OAuth2 app. For example: 950722000000343754', optional: true) - ->param('clientSecret', null, new Nullable(new Text(512, 0)), 'Client Secret of Discord OAuth2 app. For example: YmPXnM000000000000000000002zFg5D', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) - ->inject('response') - ->inject('dbForPlatform') - ->inject('project') - ->inject('authorization') - ->callback($this->action(...)); + return 'Discord'; } - public function action( - ?string $clientId, - ?string $clientSecret, - ?bool $enabled, - Response $response, - Database $dbForPlatform, - Document $project, - Authorization $authorization - ): void { - $providerId = self::getProviderId(); - if(!(\in_array($providerId, \array_keys(Config::getParam('oAuthProviders'))))) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Provider ' . $providerId . ' is not supported by server configuration.'); - } + public static function getResponseModel(): string + { + return Response::MODEL_OAUTH2_DISCORD; + } - $oAuthProviders = $project->getAttribute('oAuthProviders', []); + public static function getClientIdDescription(): string + { + return 'Client ID of Discord OAuth2 app. For example: 950722000000343754'; + } - $appIdKey = $providerId . 'Appid'; - $appSecretKey = $providerId . 'Secret'; - $enabledKey = $providerId . 'Enabled'; - - if (!\is_null($clientId)) { - $oAuthProviders[$appIdKey] = $clientId; - } - - if (!\is_null($clientSecret)) { - $oAuthProviders[$appSecretKey] = $clientSecret; - } - - if (!\is_null($enabled)) { - $oAuthProviders[$enabledKey] = $enabled; - } - - if($enabled === true || \is_null($enabled)) { - try { - if(empty($oAuthProviders[$appIdKey]) || empty($oAuthProviders[$appSecretKey])) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Client ID and Client Secret are required when enabling OAuth2 provider.'); - } - - $providerClass = self::getProviderClass(); - $providerInstance = new $providerClass(appId: $oAuthProviders[$appIdKey], appSecret: $oAuthProviders[$appSecretKey], callback: '', state: [], scopes: []); - - $providerInstance->verifyCredentials(); - - $oAuthProviders[$enabledKey] = true; - } catch(\Throwable $err) { - if($enabled === true) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage()); - } - } - } - - $updates = new Document([ - 'oAuthProviders' => $oAuthProviders - ]); - - $project = $authorization->skip(fn() => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); - - $response->dynamic(new Document([ - '$id' => $providerId, - 'enabled' => $oAuthProviders[$enabledKey] ?? false, - 'clientId' => $oAuthProviders[$appIdKey] ?? '', - 'clientSecret' => $oAuthProviders[$appSecretKey] ?? '', - ]), Response::MODEL_OAUTH2_DISCORD); + public static function getClientSecretDescription(): string + { + return 'Client Secret of Discord OAuth2 app. For example: YmPXnM000000000000000000002zFg5D'; } } diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Figma/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Figma/Update.php index 34ec34be9d..c19b9fb30f 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Figma/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Figma/Update.php @@ -3,142 +3,38 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Figma; use Appwrite\Auth\OAuth2\Figma; -use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; -use Appwrite\SDK\AuthType; -use Appwrite\SDK\Method; -use Appwrite\SDK\Response as SDKResponse; +use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base; use Appwrite\Utopia\Response; -use Utopia\Config\Config; -use Utopia\Database\Database; -use Utopia\Database\Document; -use Utopia\Database\Validator\Authorization; -use Utopia\Platform\Scope\HTTP; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; -use Utopia\Validator\Text; -class Update extends Action +class Update extends Base { - use HTTP; - - public static function getName() - { - return 'updateProjectOAuth2Figma'; - } - public static function getProviderId(): string { return 'figma'; } - /** - * @return class-string - */ public static function getProviderClass(): string { return Figma::class; } - public function __construct() + public static function getProviderLabel(): string { - $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/project/oauth2/figma') - ->desc('Update project OAuth2 Figma') - ->groups(['api', 'project']) - ->label('scope', 'oauth2.write') - ->label('event', 'oauth2.figma.update') - ->label('audits.event', 'project.oauth2.figma.update') - ->label('audits.resource', 'project.oauth2/{response.$id}') - ->label('sdk', new Method( - namespace: 'project', - group: 'oauth2', - name: 'updateOAuth2Figma', - description: <<param('clientId', null, new Nullable(new Text(256, 0)), 'Client ID of Figma OAuth2 app. For example: byay5H0000000000VtiI40', optional: true) - ->param('clientSecret', null, new Nullable(new Text(512, 0)), 'Client Secret of Figma OAuth2 app. For example: yEpOYn0000000000000000004iIsU5', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) - ->inject('response') - ->inject('dbForPlatform') - ->inject('project') - ->inject('authorization') - ->callback($this->action(...)); + return 'Figma'; } - public function action( - ?string $clientId, - ?string $clientSecret, - ?bool $enabled, - Response $response, - Database $dbForPlatform, - Document $project, - Authorization $authorization - ): void { - $providerId = self::getProviderId(); - if(!(\in_array($providerId, \array_keys(Config::getParam('oAuthProviders'))))) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Provider ' . $providerId . ' is not supported by server configuration.'); - } + public static function getResponseModel(): string + { + return Response::MODEL_OAUTH2_FIGMA; + } - $oAuthProviders = $project->getAttribute('oAuthProviders', []); + public static function getClientIdDescription(): string + { + return 'Client ID of Figma OAuth2 app. For example: byay5H0000000000VtiI40'; + } - $appIdKey = $providerId . 'Appid'; - $appSecretKey = $providerId . 'Secret'; - $enabledKey = $providerId . 'Enabled'; - - if (!\is_null($clientId)) { - $oAuthProviders[$appIdKey] = $clientId; - } - - if (!\is_null($clientSecret)) { - $oAuthProviders[$appSecretKey] = $clientSecret; - } - - if (!\is_null($enabled)) { - $oAuthProviders[$enabledKey] = $enabled; - } - - if($enabled === true || \is_null($enabled)) { - try { - if(empty($oAuthProviders[$appIdKey]) || empty($oAuthProviders[$appSecretKey])) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Client ID and Client Secret are required when enabling OAuth2 provider.'); - } - - $providerClass = self::getProviderClass(); - $providerInstance = new $providerClass(appId: $oAuthProviders[$appIdKey], appSecret: $oAuthProviders[$appSecretKey], callback: '', state: [], scopes: []); - - $providerInstance->verifyCredentials(); - - $oAuthProviders[$enabledKey] = true; - } catch(\Throwable $err) { - if($enabled === true) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage()); - } - } - } - - $updates = new Document([ - 'oAuthProviders' => $oAuthProviders - ]); - - $project = $authorization->skip(fn() => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); - - $response->dynamic(new Document([ - '$id' => $providerId, - 'enabled' => $oAuthProviders[$enabledKey] ?? false, - 'clientId' => $oAuthProviders[$appIdKey] ?? '', - 'clientSecret' => $oAuthProviders[$appSecretKey] ?? '', - ]), Response::MODEL_OAUTH2_FIGMA); + public static function getClientSecretDescription(): string + { + return 'Client Secret of Figma OAuth2 app. For example: yEpOYn0000000000000000004iIsU5'; } } diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/GitHub/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/GitHub/Update.php index ffdb2c78d0..4490fa90cd 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/GitHub/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/GitHub/Update.php @@ -3,142 +3,38 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\GitHub; use Appwrite\Auth\OAuth2\Github; -use Appwrite\Extend\Exception; -use Appwrite\Platform\Action; -use Appwrite\SDK\AuthType; -use Appwrite\SDK\Method; -use Appwrite\SDK\Response as SDKResponse; +use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base; use Appwrite\Utopia\Response; -use Utopia\Config\Config; -use Utopia\Database\Database; -use Utopia\Database\Document; -use Utopia\Database\Validator\Authorization; -use Utopia\Platform\Scope\HTTP; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; -use Utopia\Validator\Text; -class Update extends Action +class Update extends Base { - use HTTP; - - public static function getName() - { - return 'updateProjectOAuth2GitHub'; - } - public static function getProviderId(): string { return 'github'; } - - /** - * @return class-string - */ + public static function getProviderClass(): string { return Github::class; } - public function __construct() + public static function getProviderLabel(): string { - $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) - ->setHttpPath('/v1/project/oauth2/github') - ->desc('Update project OAuth2 GitHub') - ->groups(['api', 'project']) - ->label('scope', 'oauth2.write') - ->label('event', 'oauth2.github.update') - ->label('audits.event', 'project.oauth2.github.update') - ->label('audits.resource', 'project.oauth2/{response.$id}') - ->label('sdk', new Method( - namespace: 'project', - group: 'oauth2', - name: 'updateOAuth2GitHub', - description: <<param('clientId', null, new Nullable(new Text(256, 0)), 'Client ID of GitHub OAuth2 app, or App ID of GitHub generic app. For example: e4d87900000000540733', optional: true) - ->param('clientSecret', null, new Nullable(new Text(512, 0)), 'Client secret of GitHub OAuth2 app, or GitHub generic app. For example: 5e07c00000000000000000000000000000198bcc', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) - ->inject('response') - ->inject('dbForPlatform') - ->inject('project') - ->inject('authorization') - ->callback($this->action(...)); + return 'GitHub'; } - public function action( - ?string $clientId, - ?string $clientSecret, - ?bool $enabled, - Response $response, - Database $dbForPlatform, - Document $project, - Authorization $authorization - ): void { - $providerId = self::getProviderId(); - if(!(\in_array($providerId, \array_keys(Config::getParam('oAuthProviders'))))) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Provider ' . $providerId . ' is not supported by server configuration.'); - } - - $oAuthProviders = $project->getAttribute('oAuthProviders', []); - - $appIdKey = $providerId . 'Appid'; - $appSecretKey = $providerId . 'Secret'; - $enabledKey = $providerId . 'Enabled'; + public static function getResponseModel(): string + { + return Response::MODEL_OAUTH2_GITHUB; + } - if (!\is_null($clientId)) { - $oAuthProviders[$appIdKey] = $clientId; - } - - if (!\is_null($clientSecret)) { - $oAuthProviders[$appSecretKey] = $clientSecret; - } + public static function getClientIdDescription(): string + { + return 'Client ID of GitHub OAuth2 app, or App ID of GitHub generic app. For example: e4d87900000000540733'; + } - if (!\is_null($enabled)) { - $oAuthProviders[$enabledKey] = $enabled; - } - - if($enabled === true || \is_null($enabled)) { - try { - if(empty($oAuthProviders[$appIdKey]) || empty($oAuthProviders[$appSecretKey])) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Client ID and Client Secret are required when enabling OAuth2 provider.'); - } - - $providerClass = self::getProviderClass(); - $providerInstance = new $providerClass(appId: $oAuthProviders[$appIdKey], appSecret: $oAuthProviders[$appSecretKey], callback: '', state: [], scopes: []); - - $providerInstance->verifyCredentials(); - - $oAuthProviders[$enabledKey] = true; - } catch(\Throwable $err) { - if($enabled === true) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage()); - } - } - } - - $updates = new Document([ - 'oAuthProviders' => $oAuthProviders - ]); - - $project = $authorization->skip(fn() => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); - - $response->dynamic(new Document([ - '$id' => $providerId, - 'enabled' => $oAuthProviders[$enabledKey] ?? false, - 'clientId' => $oAuthProviders[$appIdKey] ?? '', - 'clientSecret' => $oAuthProviders[$appSecretKey] ?? '', - ]), Response::MODEL_OAUTH2_GITHUB); + public static function getClientSecretDescription(): string + { + return 'Client secret of GitHub OAuth2 app, or GitHub generic app. For example: 5e07c00000000000000000000000000000198bcc'; } }