mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
GitHub oauth endpoint
This commit is contained in:
@@ -55,6 +55,8 @@ $admins = [
|
||||
'tables.write',
|
||||
'platforms.read',
|
||||
'platforms.write',
|
||||
'oauth2.read',
|
||||
'oauth2.write',
|
||||
'mocks.read',
|
||||
'mocks.write',
|
||||
'policies.read',
|
||||
|
||||
@@ -228,4 +228,12 @@ return [ // List of publicly visible scopes
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s templates",
|
||||
],
|
||||
"oauth2.read" => [
|
||||
"description" =>
|
||||
"Access to read project\'s OAuth2 configuration",
|
||||
],
|
||||
"oauth2.write" => [
|
||||
"description" =>
|
||||
"Access to update project\'s OAuth2 configuration",
|
||||
],
|
||||
];
|
||||
|
||||
@@ -50,6 +50,13 @@ abstract class OAuth2
|
||||
$this->addScope($scope);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth credentials are valid
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
abstract public function verifyCredentials(): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
use Utopia\Fetch\Client as FetchClient;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
@@ -219,4 +220,33 @@ class Github extends OAuth2
|
||||
$repository = \json_decode($repository, true);
|
||||
return $repository;
|
||||
}
|
||||
|
||||
public function verifyCredentials(): void {
|
||||
$client = new FetchClient();
|
||||
$client->addHeader('Accept', 'application/json');
|
||||
|
||||
$response = $client->fetch(
|
||||
url: 'https://github.com/login/oauth/access_token',
|
||||
method: FetchClient::METHOD_POST,
|
||||
query: [
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'code' => 'intentionally-invalid-code',
|
||||
'redirect_uri' => 'intentionally-invalid-redirect',
|
||||
]
|
||||
);
|
||||
|
||||
$json = \json_decode($response->getBody(), true);
|
||||
|
||||
if (isset($json['error']) && $json['error'] === "Not Found") {
|
||||
throw new \Exception('GitHub application with provided Client ID is does not exist.');
|
||||
}
|
||||
|
||||
if (isset($json['error']) && $json['error'] === "incorrect_client_credentials") {
|
||||
throw new \Exception('GitHub application with provided Client ID is valid, but the provided Client Secret is incorrect.');
|
||||
}
|
||||
|
||||
// We still expect error, like redirect_uri_mismatch or bad_verification_code,
|
||||
// but that indicates valid credentials
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
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\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
|
||||
{
|
||||
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()
|
||||
{
|
||||
$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: <<<EOT
|
||||
Update the project OAuth2 GitHub configuration.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_OAUTH2_GITHUB,
|
||||
)
|
||||
],
|
||||
))
|
||||
->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(...));
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
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_GITHUB);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\Get as GetMockPhone
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\Update as UpdateMockPhone;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\MockPhone\XList as ListMockPhones;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android\Create as CreateAndroidPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\GitHub\Update as UpdateOAuth2GitHub;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android\Update as UpdateAndroidPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple\Create as CreateApplePlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple\Update as UpdateApplePlatform;
|
||||
@@ -129,5 +130,8 @@ class Http extends Service
|
||||
|
||||
// Auth Methods
|
||||
$this->addAction(UpdateAuthMethod::getName(), new UpdateAuthMethod());
|
||||
|
||||
// OAuth2
|
||||
$this->addAction(UpdateOAuth2GitHub::getName(), new UpdateOAuth2GitHub());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +391,8 @@ class Migrations extends Action
|
||||
'keys.write',
|
||||
'platforms.read',
|
||||
'platforms.write',
|
||||
'oauth2.read',
|
||||
'oauth2.write',
|
||||
'mocks.read',
|
||||
'mocks.write',
|
||||
'policies.read',
|
||||
|
||||
@@ -90,6 +90,8 @@ const API_SCOPES = [
|
||||
'tokens.write',
|
||||
'platforms.read',
|
||||
'platforms.write',
|
||||
'oauth2.read',
|
||||
'oauth2.write',
|
||||
];
|
||||
|
||||
const BASE_PERMISSIONS = [
|
||||
|
||||
@@ -169,6 +169,8 @@ trait ProjectCustom
|
||||
'keys.write',
|
||||
'platforms.read',
|
||||
'platforms.write',
|
||||
'oauth2.read',
|
||||
'oauth2.write',
|
||||
'mocks.read',
|
||||
'mocks.write',
|
||||
'policies.read',
|
||||
|
||||
Reference in New Issue
Block a user