mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.8.x' into chore-usecases-enums
This commit is contained in:
@@ -26,8 +26,8 @@ unset($common['files']);
|
||||
$collections = [
|
||||
'buckets' => $buckets,
|
||||
'databases' => $databases,
|
||||
'projects' => array_merge($projects, $common),
|
||||
'console' => array_merge($platform, $common),
|
||||
'projects' => array_merge_recursive($projects, $common),
|
||||
'console' => array_merge_recursive($platform, $common),
|
||||
'logs' => $logs,
|
||||
];
|
||||
|
||||
|
||||
@@ -633,29 +633,7 @@ $platformCollections = [
|
||||
'name' => 'keys',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceType',
|
||||
'$id' => ID::custom('resourceType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -666,7 +644,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceId',
|
||||
'$id' => ID::custom('resourceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -677,7 +655,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceInternalId',
|
||||
'$id' => ID::custom('resourceInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -756,21 +734,14 @@ $platformCollections = [
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_resource',
|
||||
'$id' => ID::custom('_key_resource'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType', 'resourceInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'$id' => ID::custom('_key_accessedAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['accessedAt'],
|
||||
'lengths' => [],
|
||||
|
||||
@@ -357,6 +357,11 @@ return [
|
||||
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::USER_JWT_AND_COOKIE_SET => [
|
||||
'name' => Exception::USER_JWT_AND_COOKIE_SET,
|
||||
'description' => 'JWT and cookie used in the same request. Use either `setJWT` or `setCookie`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::API_KEY_EXPIRED => [
|
||||
'name' => Exception::API_KEY_EXPIRED,
|
||||
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
|
||||
@@ -1074,6 +1079,11 @@ return [
|
||||
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::ACCOUNT_KEY_EXPIRED => [
|
||||
'name' => Exception::ACCOUNT_KEY_EXPIRED,
|
||||
'description' => 'The account API key has expired. Please generate a new key using the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::ROUTER_HOST_NOT_FOUND => [
|
||||
'name' => Exception::ROUTER_HOST_NOT_FOUND,
|
||||
'description' => 'Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again.',
|
||||
@@ -1328,4 +1338,19 @@ return [
|
||||
'description' => 'Target has an invalid provider type.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_ID_MISSING => [
|
||||
'name' => Exception::USER_ID_MISSING,
|
||||
'description' => 'When using account API key, make sure to pass x-appwrite-user header with your user ID.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::ORGANIZATION_ID_MISSING => [
|
||||
'name' => Exception::ORGANIZATION_ID_MISSING,
|
||||
'description' => 'When using organization API key, make sure to pass x-appwrite-organization header with your organization ID.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::PROJECT_ID_MISSING => [
|
||||
'name' => Exception::PROJECT_ID_MISSING,
|
||||
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
|
||||
'code' => 403,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
// List of scopes for Account API keys (Tokens)
|
||||
|
||||
return [
|
||||
"account" => [
|
||||
"description" => 'Access to manage account, its organizations, sessions, tokens, and billing.',
|
||||
],
|
||||
"teams.read" => [
|
||||
"description" => 'Access to read account\'s organizations.',
|
||||
],
|
||||
"teams.write" => [
|
||||
"description" => 'Access to create, update and delete account\'s organizations and its memberships.',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
// List of scopes for organization (teams) API keys
|
||||
|
||||
return [
|
||||
"platforms.read" => [
|
||||
"description" => 'Access to read project\'s platforms',
|
||||
],
|
||||
"platforms.write" => [
|
||||
"description" =>
|
||||
'Access to create, update, and delete project\'s platforms',
|
||||
],
|
||||
"projects.read" => [
|
||||
"description" => 'Access to read organization\'s projects',
|
||||
],
|
||||
"projects.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete projects in organization",
|
||||
],
|
||||
"keys.read" => [
|
||||
"description" => 'Access to read project\'s API keys',
|
||||
],
|
||||
"keys.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s API keys",
|
||||
],
|
||||
"devKeys.read" => [
|
||||
"description" => 'Access to read project\'s development keys',
|
||||
],
|
||||
"devKeys.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s development keys",
|
||||
],
|
||||
"webhooks.read" => [
|
||||
"description" =>
|
||||
"Access to read project\'s webhooks",
|
||||
],
|
||||
"webhooks.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s webhooks",
|
||||
],
|
||||
];
|
||||
+21
-1
@@ -227,7 +227,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '13.0.1',
|
||||
'version' => '13.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -250,6 +250,26 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'markdown',
|
||||
'name' => 'Markdown',
|
||||
'version' => '0.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-md.git',
|
||||
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
'prism' => 'markdown',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
|
||||
'gitRepoName' => 'sdk-for-md',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'repoBranch' => 'main',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -1483,7 +1483,7 @@ App::post('/v1/projects/:projectId/keys')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
@@ -1502,9 +1502,6 @@ App::post('/v1/projects/:projectId/keys')
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
// TODO: @hmacr Remove `projectInternalId` and `projectId` column writes before deleting the column.
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'resourceInternalId' => $project->getSequence(),
|
||||
'resourceId' => $project->getId(),
|
||||
'resourceType' => 'projects',
|
||||
@@ -1628,7 +1625,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
@@ -1729,7 +1726,7 @@ App::post('/v1/projects/:projectId/jwts')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
|
||||
@@ -433,15 +433,22 @@ App::delete('/v1/teams/:teamId')
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
|
||||
}
|
||||
|
||||
// Sync delete
|
||||
$deletes = new Deletes();
|
||||
$deletes->deleteMemberships($getProjectDB, $team, $project);
|
||||
|
||||
// Async delete
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_TEAM_PROJECTS)
|
||||
->setDocument($team);
|
||||
->setDocument($team)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setPayload($response->output($team, Response::MODEL_TEAM))
|
||||
|
||||
@@ -1228,7 +1228,7 @@ App::error()
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
case 'Utopia\Exception':
|
||||
case Utopia\Exception::class:
|
||||
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
@@ -1239,10 +1239,10 @@ App::error()
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Authorization':
|
||||
case Utopia\Database\Exception\Authorization::class:
|
||||
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\Timeout':
|
||||
case Utopia\Database\Exception\Timeout::class:
|
||||
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
|
||||
break;
|
||||
}
|
||||
@@ -1503,7 +1503,7 @@ App::error()
|
||||
$template = $error->getView() ?? (($route) ? $route->getLabel('error', null) : null);
|
||||
|
||||
// TODO: Ideally use group 'api' here, but all wildcard routes seem to have 'api' at the moment
|
||||
if (!\str_starts_with($route->getPath(), '/v1')) {
|
||||
if (empty($route) || !\str_starts_with($route->getPath(), '/v1')) {
|
||||
$template = __DIR__ . '/../views/general/error.phtml';
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$scopes = array_keys(Config::getParam('scopes'));
|
||||
$scopes = array_keys(Config::getParam('projectScopes'));
|
||||
|
||||
$key = new Document([
|
||||
'$id' => ID::unique(),
|
||||
@@ -200,9 +200,6 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
// TODO: @hmacr Remove `projectInternalId` and `projectId` column writes before deleting the column.
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'resourceInternalId' => $project->getSequence(),
|
||||
'resourceId' => $project->getId(),
|
||||
'resourceType' => 'projects',
|
||||
|
||||
@@ -157,10 +157,6 @@ App::init()
|
||||
|
||||
// Step 5: API Key Authentication
|
||||
if (!empty($apiKey)) {
|
||||
// Verify no user session exists simultaneously
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
// Check if key is expired
|
||||
if ($apiKey->isExpired()) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
@@ -189,23 +185,38 @@ App::init()
|
||||
}
|
||||
|
||||
// For standard keys, update last accessed time
|
||||
if ($apiKey->getType() === API_KEY_STANDARD) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
if (\in_array($apiKey->getType(), [API_KEY_STANDARD, API_KEY_ORGANIZATION, API_KEY_ACCOUNT])) {
|
||||
$dbKey = null;
|
||||
if (!empty($apiKey->getProjectId())) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
} elseif (!empty($apiKey->getUserId())) {
|
||||
$dbKey = $user->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
} elseif (!empty($apiKey->getTeamId())) {
|
||||
$dbKey = $team->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$dbKey) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$updates = new Document();
|
||||
|
||||
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
|
||||
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$dbKey->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$updates->setAttribute('accessedAt', DateTime::now());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
@@ -216,12 +227,21 @@ App::init()
|
||||
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
$sdks[] = $sdk;
|
||||
$dbKey->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$dbKey->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$updates->setAttribute('sdks', $sdks);
|
||||
$updates->setAttribute('accessedAt', Datetime::now());
|
||||
}
|
||||
}
|
||||
|
||||
if (!$updates->isEmpty()) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('keys', $dbKey->getId(), $updates));
|
||||
|
||||
if (!empty($apiKey->getProjectId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
} elseif (!empty($apiKey->getUserId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('users', $user->getId()));
|
||||
} elseif (!empty($apiKey->getTeamId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapt
|
||||
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
|
||||
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
|
||||
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
|
||||
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
|
||||
Config::load('projectScopes', __DIR__ . '/../config/scopes/project.php', $configAdapter);
|
||||
Config::load('organizationScopes', __DIR__ . '/../config/scopes/organization.php', $configAdapter);
|
||||
Config::load('accountScopes', __DIR__ . '/../config/scopes/account.php', $configAdapter);
|
||||
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
|
||||
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
|
||||
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
|
||||
|
||||
@@ -194,6 +194,7 @@ const DELETE_TYPE_SITES = 'sites';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAMS = 'teams';
|
||||
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_EXECUTIONS_LIMIT = 'executionsLimit';
|
||||
@@ -249,6 +250,8 @@ const MESSAGE_TYPE_PUSH = 'push';
|
||||
// API key types
|
||||
const API_KEY_STANDARD = 'standard';
|
||||
const API_KEY_DYNAMIC = 'dynamic';
|
||||
const API_KEY_ORGANIZATION = 'organization';
|
||||
const API_KEY_ACCOUNT = 'account';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
|
||||
@@ -433,3 +433,34 @@ Database::addFilter(
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryOrganizationKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('keys', [
|
||||
Query::equal('resourceType', ['teams']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryAccountKeys',
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('keys', [
|
||||
Query::equal('resourceType', ['users']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_L
|
||||
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
|
||||
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
|
||||
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
|
||||
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, false));
|
||||
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
|
||||
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
|
||||
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
|
||||
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
|
||||
|
||||
+62
-4
@@ -343,6 +343,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
|
||||
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
|
||||
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
|
||||
* overwriting the previous value.
|
||||
* 7. If account API key is passed, use user of the account API key as long as user ID header matches too
|
||||
*/
|
||||
|
||||
$authorization->setDefaultStatus(true);
|
||||
@@ -419,12 +420,17 @@ App::setResource('user', function (string $mode, Document $project, Document $co
|
||||
// }
|
||||
$authJWT = $request->getHeader('x-appwrite-jwt', '');
|
||||
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_JWT_AND_COOKIE_SET);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
|
||||
}
|
||||
|
||||
$jwtUserId = $payload['userId'] ?? '';
|
||||
if (!empty($jwtUserId)) {
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
@@ -440,6 +446,34 @@ App::setResource('user', function (string $mode, Document $project, Document $co
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Account based on account API key
|
||||
$accountKey = $request->getHeader('x-appwrite-key', '');
|
||||
$accountKeyUserId = $request->getHeader('x-appwrite-user', '');
|
||||
if (!empty($accountKeyUserId) && !empty($accountKey)) {
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
|
||||
$accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
|
||||
if (!$accountKeyUser->isEmpty()) {
|
||||
$key = $accountKeyUser->find(
|
||||
key: 'secret',
|
||||
find: $accountKey,
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
if (!empty($key)) {
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
|
||||
throw new Exception(Exception::ACCOUNT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
$user = $accountKeyUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
$dbForPlatform->setMetadata('user', $user->getId());
|
||||
|
||||
@@ -1227,7 +1261,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
} else {
|
||||
$route = $utopia->match($request);
|
||||
$path = $route->getPath();
|
||||
$path = !empty($route) ? $route->getPath() : $request->getURI();
|
||||
if (str_starts_with($path, '/v1/projects/:projectId')) {
|
||||
$uri = $request->getURI();
|
||||
$pid = explode('/', $uri)[3];
|
||||
@@ -1282,15 +1316,39 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
return '';
|
||||
}, ['request', 'apiKey']);
|
||||
|
||||
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
|
||||
App::setResource('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
|
||||
$key = $request->getHeader('x-appwrite-key');
|
||||
|
||||
if (empty($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Key::decode($project, $key);
|
||||
}, ['request', 'project']);
|
||||
$key = Key::decode($project, $team, $user, $key);
|
||||
|
||||
$userHeader = $request->getHeader('x-appwrite-user');
|
||||
$organizationHeader = $request->getHeader('x-appwrite-organization');
|
||||
$projectHeader = $request->getHeader('x-appwrite-project');
|
||||
|
||||
if (!empty($key->getProjectId())) {
|
||||
if (empty($projectHeader) || $projectHeader !== $key->getProjectId()) {
|
||||
throw new Exception(Exception::PROJECT_ID_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($key->getUserId())) {
|
||||
if (empty($userHeader) || $userHeader !== $key->getUserId()) {
|
||||
throw new Exception(Exception::USER_ID_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($key->getTeamId())) {
|
||||
if (empty($organizationHeader) || $organizationHeader !== $key->getTeamId()) {
|
||||
throw new Exception(Exception::ORGANIZATION_ID_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}, ['request', 'project', 'team', 'user']);
|
||||
|
||||
App::setResource('executor', fn () => new Executor());
|
||||
|
||||
|
||||
Generated
+164
-101
@@ -798,6 +798,68 @@
|
||||
},
|
||||
"time": "2026-01-12T17:58:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "halaxa/json-machine",
|
||||
"version": "1.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/halaxa/json-machine.git",
|
||||
"reference": "8bf0b0ff6ff60ab480778eaa5ad7d505b442c2d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/halaxa/json-machine/zipball/8bf0b0ff6ff60ab480778eaa5ad7d505b442c2d4",
|
||||
"reference": "8bf0b0ff6ff60ab480778eaa5ad7d505b442c2d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "7.2 - 8.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-json": "To run JSON Machine out of the box without custom decoders.",
|
||||
"guzzlehttp/guzzle": "To run example with GuzzleHttp"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"JsonMachine\\": "src/"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"src/autoloader.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Filip Halaxa",
|
||||
"email": "filip@halaxa.cz"
|
||||
}
|
||||
],
|
||||
"description": "Efficient, easy-to-use and fast JSON pull parser",
|
||||
"support": {
|
||||
"issues": "https://github.com/halaxa/json-machine/issues",
|
||||
"source": "https://github.com/halaxa/json-machine/tree/1.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://ko-fi.com/G2G57KTE4",
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-05T14:53:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.24.1",
|
||||
@@ -1236,16 +1298,16 @@
|
||||
},
|
||||
{
|
||||
"name": "open-telemetry/api",
|
||||
"version": "1.7.1",
|
||||
"version": "1.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentelemetry-php/api.git",
|
||||
"reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4"
|
||||
"reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4",
|
||||
"reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/df5197c6fd0ddd8e9883b87de042d9341300e2ad",
|
||||
"reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1255,7 +1317,7 @@
|
||||
"symfony/polyfill-php82": "^1.26"
|
||||
},
|
||||
"conflict": {
|
||||
"open-telemetry/sdk": "<=1.0.8"
|
||||
"open-telemetry/sdk": "<=1.11"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -1302,7 +1364,7 @@
|
||||
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||
},
|
||||
"time": "2025-10-19T10:49:48+00:00"
|
||||
"time": "2026-01-21T04:14:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "open-telemetry/context",
|
||||
@@ -1492,16 +1554,16 @@
|
||||
},
|
||||
{
|
||||
"name": "open-telemetry/sdk",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentelemetry-php/sdk.git",
|
||||
"reference": "d91f21addcdb42da9a451c002777f8318432461a"
|
||||
"reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/d91f21addcdb42da9a451c002777f8318432461a",
|
||||
"reference": "d91f21addcdb42da9a451c002777f8318432461a",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/7f1bd524465c1ca42755a9ef1143ba09913f5be0",
|
||||
"reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1542,7 +1604,7 @@
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "1.9.x-dev"
|
||||
"dev-main": "1.12.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -1585,20 +1647,20 @@
|
||||
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||
},
|
||||
"time": "2026-01-15T11:21:03+00:00"
|
||||
"time": "2026-01-21T04:14:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "open-telemetry/sem-conv",
|
||||
"version": "1.37.0",
|
||||
"version": "1.38.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentelemetry-php/sem-conv.git",
|
||||
"reference": "8da7ec497c881e39afa6657d72586e27efbd29a1"
|
||||
"reference": "e613bc640a407def4991b8a936a9b27edd9a3240"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1",
|
||||
"reference": "8da7ec497c881e39afa6657d72586e27efbd29a1",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240",
|
||||
"reference": "e613bc640a407def4991b8a936a9b27edd9a3240",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1638,11 +1700,11 @@
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
|
||||
"docs": "https://opentelemetry.io/docs/php",
|
||||
"docs": "https://opentelemetry.io/docs/languages/php",
|
||||
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||
},
|
||||
"time": "2025-09-03T12:08:10+00:00"
|
||||
"time": "2026-01-21T04:14:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
@@ -2004,16 +2066,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.48",
|
||||
"version": "3.0.49",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89"
|
||||
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89",
|
||||
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9",
|
||||
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2094,7 +2156,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.48"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.49"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2110,7 +2172,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-15T11:51:42+00:00"
|
||||
"time": "2026-01-27T09:17:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -2673,16 +2735,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v7.4.3",
|
||||
"version": "v7.4.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "d01dfac1e0dc99f18da48b18101c23ce57929616"
|
||||
"reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616",
|
||||
"reference": "d01dfac1e0dc99f18da48b18101c23ce57929616",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f",
|
||||
"reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2750,7 +2812,7 @@
|
||||
"http"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.4.3"
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.4.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2770,7 +2832,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-23T14:50:43+00:00"
|
||||
"time": "2026-01-27T16:16:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
@@ -3553,16 +3615,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "2.0.4",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "1301ab2607667b9f86456f86895f3e26f8c0c9a7"
|
||||
"reference": "8e0540aa939968418ee3ad2b2c305992a771e142"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/1301ab2607667b9f86456f86895f3e26f8c0c9a7",
|
||||
"reference": "1301ab2607667b9f86456f86895f3e26f8c0c9a7",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/8e0540aa939968418ee3ad2b2c305992a771e142",
|
||||
"reference": "8e0540aa939968418ee3ad2b2c305992a771e142",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3596,9 +3658,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/2.0.4"
|
||||
"source": "https://github.com/utopia-php/audit/tree/2.1.0"
|
||||
},
|
||||
"time": "2026-01-14T07:22:46+00:00"
|
||||
"time": "2026-01-22T12:40:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/auth",
|
||||
@@ -3899,16 +3961,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "4.5.2",
|
||||
"version": "4.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "8e6a033d4da09a2f2ac1f79fd85fcfa2da018d23"
|
||||
"reference": "53394759c44067e9db4660635765e2056f83788c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/8e6a033d4da09a2f2ac1f79fd85fcfa2da018d23",
|
||||
"reference": "8e6a033d4da09a2f2ac1f79fd85fcfa2da018d23",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/53394759c44067e9db4660635765e2056f83788c",
|
||||
"reference": "53394759c44067e9db4660635765e2056f83788c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3951,9 +4013,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/4.5.2"
|
||||
"source": "https://github.com/utopia-php/database/tree/4.6.2"
|
||||
},
|
||||
"time": "2026-01-15T04:23:30+00:00"
|
||||
"time": "2026-01-22T07:14:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -4516,22 +4578,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "3fe751902012d09d323420cd3523be1ed855e868"
|
||||
"reference": "f358db6fb6a01d855bbed39e283387069e4f277d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/3fe751902012d09d323420cd3523be1ed855e868",
|
||||
"reference": "3fe751902012d09d323420cd3523be1ed855e868",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/f358db6fb6a01d855bbed39e283387069e4f277d",
|
||||
"reference": "f358db6fb6a01d855bbed39e283387069e4f277d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"appwrite/appwrite": "19.*",
|
||||
"ext-curl": "*",
|
||||
"ext-openssl": "*",
|
||||
"halaxa/json-machine": "^1.2",
|
||||
"php": ">=8.1",
|
||||
"utopia-php/console": "0.0.*",
|
||||
"utopia-php/database": "4.*",
|
||||
@@ -4565,9 +4628,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.4.4"
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.4.6"
|
||||
},
|
||||
"time": "2026-01-16T10:00:07+00:00"
|
||||
"time": "2026-01-20T11:07:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -5057,28 +5120,28 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/swoole.git",
|
||||
"reference": "95a937acb393dbf95cccba239d55886e2848ab0b"
|
||||
"reference": "c5ce710dfffc4df09bf3e7aea2d1e55c53e77a95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/95a937acb393dbf95cccba239d55886e2848ab0b",
|
||||
"reference": "95a937acb393dbf95cccba239d55886e2848ab0b",
|
||||
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/c5ce710dfffc4df09bf3e7aea2d1e55c53e77a95",
|
||||
"reference": "c5ce710dfffc4df09bf3e7aea2d1e55c53e77a95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-swoole": "*",
|
||||
"php": ">=8.0",
|
||||
"ext-swoole": "6.*",
|
||||
"php": ">=8.1",
|
||||
"utopia-php/framework": "0.33.37"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"swoole/ide-helper": "5.0.2"
|
||||
"swoole/ide-helper": "6.0.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -5102,9 +5165,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/swoole/issues",
|
||||
"source": "https://github.com/utopia-php/swoole/tree/1.0.0"
|
||||
"source": "https://github.com/utopia-php/swoole/tree/1.0.1"
|
||||
},
|
||||
"time": "2026-01-14T14:00:11+00:00"
|
||||
"time": "2026-01-28T12:43:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/system",
|
||||
@@ -5482,16 +5545,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.8.17",
|
||||
"version": "1.8.21",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "1bc5a39bf87d3c2064f2f8d45fa712340338bc41"
|
||||
"reference": "1b47b2c794811c565f8b5e7eeaa19f749bcbeb6b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/1bc5a39bf87d3c2064f2f8d45fa712340338bc41",
|
||||
"reference": "1bc5a39bf87d3c2064f2f8d45fa712340338bc41",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/1b47b2c794811c565f8b5e7eeaa19f749bcbeb6b",
|
||||
"reference": "1b47b2c794811c565f8b5e7eeaa19f749bcbeb6b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5527,9 +5590,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.8.17"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.8.21"
|
||||
},
|
||||
"time": "2026-01-19T12:13:41+00:00"
|
||||
"time": "2026-01-26T04:42:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
@@ -6709,16 +6772,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.6.31",
|
||||
"version": "9.6.34",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "945d0b7f346a084ce5549e95289962972c4272e5"
|
||||
"reference": "b36f02317466907a230d3aa1d34467041271ef4a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5",
|
||||
"reference": "945d0b7f346a084ce5549e95289962972c4272e5",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a",
|
||||
"reference": "b36f02317466907a230d3aa1d34467041271ef4a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6740,7 +6803,7 @@
|
||||
"phpunit/php-timer": "^5.0.3",
|
||||
"sebastian/cli-parser": "^1.0.2",
|
||||
"sebastian/code-unit": "^1.0.8",
|
||||
"sebastian/comparator": "^4.0.9",
|
||||
"sebastian/comparator": "^4.0.10",
|
||||
"sebastian/diff": "^4.0.6",
|
||||
"sebastian/environment": "^5.1.5",
|
||||
"sebastian/exporter": "^4.0.8",
|
||||
@@ -6792,7 +6855,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6816,7 +6879,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-06T07:45:52+00:00"
|
||||
"time": "2026-01-27T05:45:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
@@ -7036,16 +7099,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/comparator",
|
||||
"version": "4.0.9",
|
||||
"version": "4.0.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/comparator.git",
|
||||
"reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5"
|
||||
"reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
|
||||
"reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
|
||||
"reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7098,7 +7161,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/comparator/issues",
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9"
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7118,7 +7181,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-10T06:51:50+00:00"
|
||||
"time": "2026-01-24T09:22:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/complexity",
|
||||
@@ -7976,16 +8039,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v8.0.3",
|
||||
"version": "v8.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587"
|
||||
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
|
||||
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
|
||||
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8042,7 +8105,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.3"
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8062,7 +8125,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-23T14:52:06+00:00"
|
||||
"time": "2026-01-13T13:06:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
@@ -8136,16 +8199,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v8.0.3",
|
||||
"version": "v8.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12"
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12",
|
||||
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8180,7 +8243,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.3"
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8200,7 +8263,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-23T14:52:06+00:00"
|
||||
"time": "2026-01-26T15:08:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
@@ -8605,16 +8668,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v8.0.3",
|
||||
"version": "v8.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "0cbbd88ec836f8757641c651bb995335846abb78"
|
||||
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78",
|
||||
"reference": "0cbbd88ec836f8757641c651bb995335846abb78",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
|
||||
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8646,7 +8709,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.3"
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8666,20 +8729,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-19T10:01:18+00:00"
|
||||
"time": "2026-01-26T15:08:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v8.0.1",
|
||||
"version": "v8.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc"
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc",
|
||||
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8736,7 +8799,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.1"
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8756,7 +8819,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-01T09:13:36+00:00"
|
||||
"time": "2026-01-12T12:37:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "textalk/websocket",
|
||||
@@ -9012,5 +9075,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.3"
|
||||
},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ services:
|
||||
- ./public:/usr/src/code/public
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/src/code/dev
|
||||
# - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
appwrite health get-queue-audits
|
||||
@@ -1,5 +1,29 @@
|
||||
# Change Log
|
||||
|
||||
## 13.1.0
|
||||
|
||||
- Mark `appwrite generate` command as stable
|
||||
- Improve permissions param to be a typesafe callback
|
||||
- Fix relationship handling in generated code
|
||||
- Fix `appwrite client` properly hanlding `--key` parameter
|
||||
- Fix `init site` not working on Windows
|
||||
|
||||
## 13.1.0-rc.3
|
||||
|
||||
- Allow generation of server side CRUD operations on databases and tables
|
||||
- Fix npm distribution failing due to missing template files in bundle
|
||||
|
||||
## 13.1.0-rc.2
|
||||
|
||||
- Update generated `databases` services to automatically initialize a client instance
|
||||
- Update generator to use handlebars templates
|
||||
|
||||
## 13.1.0-rc.1
|
||||
|
||||
- Feat: `appwrite generate` command to create a fully typesafe SDK for your Appwrite project
|
||||
- Chore: improve creation of columns during table creation by passing them directly instead of creating them one by one
|
||||
- Improved config validation by adding extra rules in zod schema
|
||||
|
||||
## 13.0.1
|
||||
|
||||
- Fix `project init` command leading to Cannot convert to BigInt error
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Initial release
|
||||
@@ -15,6 +15,8 @@ class Key
|
||||
{
|
||||
public function __construct(
|
||||
protected string $projectId,
|
||||
protected string $teamId,
|
||||
protected string $userId,
|
||||
protected string $type,
|
||||
protected string $role,
|
||||
protected array $scopes,
|
||||
@@ -34,6 +36,16 @@ class Key
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
public function getUserId(): string
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getTeamId(): string
|
||||
{
|
||||
return $this->teamId;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
@@ -95,13 +107,12 @@ class Key
|
||||
* Decode the given secret key into a Key object, containing the project ID, type, role, scopes, and name.
|
||||
* Can be a stored API key or a dynamic key (JWT).
|
||||
*
|
||||
* @param Document $project
|
||||
* @param string $key
|
||||
* @return Key
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function decode(
|
||||
Document $project,
|
||||
Document $team,
|
||||
Document $user,
|
||||
string $key
|
||||
): Key {
|
||||
if (\str_contains($key, '_')) {
|
||||
@@ -118,6 +129,8 @@ class Key
|
||||
|
||||
$guestKey = new Key(
|
||||
$project->getId(),
|
||||
'',
|
||||
'',
|
||||
$type,
|
||||
User::ROLE_GUESTS,
|
||||
$roles[User::ROLE_GUESTS]['scopes'] ?? [],
|
||||
@@ -133,6 +146,7 @@ class Key
|
||||
leeway: 0
|
||||
);
|
||||
|
||||
$payload = [];
|
||||
try {
|
||||
$payload = $jwtObj->decode($secret);
|
||||
} catch (JWTException) {
|
||||
@@ -155,6 +169,8 @@ class Key
|
||||
|
||||
return new Key(
|
||||
$projectId,
|
||||
'',
|
||||
'',
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
@@ -188,12 +204,86 @@ class Key
|
||||
|
||||
return new Key(
|
||||
$project->getId(),
|
||||
'',
|
||||
'',
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
$name,
|
||||
$expired
|
||||
);
|
||||
case API_KEY_ACCOUNT:
|
||||
$key = $user->find(
|
||||
key: 'secret',
|
||||
find: $key,
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
// Invalid key
|
||||
if (!$key) {
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
$expired = false;
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
$expired = true;
|
||||
}
|
||||
|
||||
$name = $key->getAttribute('name', 'UNKNOWN');
|
||||
|
||||
$role = User::ROLE_USERS;
|
||||
|
||||
$scopes = $key->getAttribute('scopes', []);
|
||||
|
||||
$key = new Key(
|
||||
'',
|
||||
'',
|
||||
$user->getId(),
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
$name,
|
||||
$expired
|
||||
);
|
||||
|
||||
return $key;
|
||||
case API_KEY_ORGANIZATION:
|
||||
$key = $team->find(
|
||||
key: 'secret',
|
||||
find: $key,
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
// Invalid key
|
||||
if (!$key) {
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
$expired = false;
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
$expired = true;
|
||||
}
|
||||
|
||||
$name = $key->getAttribute('name', 'UNKNOWN');
|
||||
|
||||
$role = User::ROLE_APPS;
|
||||
|
||||
$scopes = $key->getAttribute('scopes', []);
|
||||
|
||||
$key = new Key(
|
||||
'',
|
||||
$team->getId(),
|
||||
'',
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
$name,
|
||||
$expired
|
||||
);
|
||||
|
||||
return $key;
|
||||
default:
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
@@ -107,7 +107,9 @@ class Exception extends \Exception
|
||||
public const string USER_DELETION_PROHIBITED = 'user_deletion_prohibited';
|
||||
public const string USER_TARGET_NOT_FOUND = 'user_target_not_found';
|
||||
public const string USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists';
|
||||
public const string USER_API_KEY_AND_SESSION_SET = 'user_key_and_session_set';
|
||||
public const string USER_API_KEY_AND_SESSION_SET = 'user_api_key_and_session_set';
|
||||
public const string USER_JWT_AND_COOKIE_SET = 'user_jwt_and_cookie_set';
|
||||
public const string USER_ID_MISSING = 'user_id_missing';
|
||||
|
||||
public const string API_KEY_EXPIRED = 'api_key_expired';
|
||||
|
||||
@@ -119,6 +121,8 @@ class Exception extends \Exception
|
||||
public const string TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
|
||||
public const string TEAM_ALREADY_EXISTS = 'team_already_exists';
|
||||
|
||||
public const string ORGANIZATION_ID_MISSING = 'organization_id_missing';
|
||||
|
||||
/** Console */
|
||||
public const string RESOURCE_ALREADY_EXISTS = 'resource_already_exists';
|
||||
|
||||
@@ -283,6 +287,7 @@ class Exception extends \Exception
|
||||
|
||||
/** Projects */
|
||||
public const string PROJECT_NOT_FOUND = 'project_not_found';
|
||||
public const string PROJECT_ID_MISSING = 'project_id_missing';
|
||||
public const string PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||
public const string PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
||||
public const string PROJECT_ALREADY_EXISTS = 'project_already_exists';
|
||||
@@ -290,6 +295,7 @@ class Exception extends \Exception
|
||||
public const string PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
||||
public const string PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||
public const string PROJECT_KEY_EXPIRED = 'project_key_expired';
|
||||
public const string ACCOUNT_KEY_EXPIRED = 'account_key_expired';
|
||||
|
||||
public const string PROJECT_SMTP_CONFIG_INVALID = 'project_smtp_config_invalid';
|
||||
|
||||
|
||||
@@ -269,59 +269,59 @@ class Mapper
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? $validator::class : '') {
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case 'Appwrite\Event\Validator\Event':
|
||||
case 'Appwrite\Event\Validator\FunctionEvent':
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case 'Appwrite\Network\Validator\DNS':
|
||||
case 'Appwrite\Network\Validator\Origin':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Utopia\Database\Validator\Key':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Utopia\Validator\Domain':
|
||||
case 'Utopia\Validator\HexColor':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\IP':
|
||||
case 'Utopia\Validator\Origin':
|
||||
case 'Utopia\Validator\Text':
|
||||
case 'Utopia\Validator\URL':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
case \Appwrite\Auth\Validator\Password::class:
|
||||
case \Appwrite\Event\Validator\Event::class:
|
||||
case \Appwrite\Event\Validator\FunctionEvent::class:
|
||||
case \Appwrite\Network\Validator\CNAME::class:
|
||||
case \Appwrite\Network\Validator\Email::class:
|
||||
case \Appwrite\Network\Validator\Redirect::class:
|
||||
case \Appwrite\Network\Validator\DNS::class:
|
||||
case \Appwrite\Network\Validator\Origin::class:
|
||||
case \Appwrite\Task\Validator\Cron::class:
|
||||
case \Appwrite\Utopia\Database\Validator\CustomId::class:
|
||||
case \Utopia\Database\Validator\Key::class:
|
||||
case \Utopia\Database\Validator\UID::class:
|
||||
case \Utopia\Validator\Domain::class:
|
||||
case \Utopia\Validator\HexColor::class:
|
||||
case \Utopia\Validator\Host::class:
|
||||
case \Utopia\Validator\IP::class:
|
||||
case \Utopia\Validator\Origin::class:
|
||||
case \Utopia\Validator\Text::class:
|
||||
case \Utopia\Validator\URL::class:
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
default:
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Tables':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Columns':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
|
||||
case 'Utopia\Database\Validator\Authorization':
|
||||
case 'Utopia\Database\Validator\Permissions':
|
||||
case 'Utopia\Database\Validator\Queries':
|
||||
case 'Utopia\Database\Validator\Queries\Documents':
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Attributes::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Base::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Buckets::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Tables::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Collections::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Columns::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Databases::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Deployments::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Executions::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Files::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Functions::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Indexes::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Installations::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Memberships::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Projects::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Rules::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Teams::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Users::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Variables::class:
|
||||
case \Utopia\Database\Validator\Authorization::class:
|
||||
case \Utopia\Database\Validator\Permissions::class:
|
||||
case \Utopia\Database\Validator\Queries::class:
|
||||
case \Utopia\Database\Validator\Queries\Documents::class:
|
||||
case \Utopia\Database\Validator\Roles::class:
|
||||
$type = Type::listOf(Type::string());
|
||||
break;
|
||||
case 'Utopia\Validator\Boolean':
|
||||
case \Utopia\Validator\Boolean::class:
|
||||
$type = Type::boolean();
|
||||
break;
|
||||
case 'Utopia\Validator\ArrayList':
|
||||
case \Utopia\Validator\ArrayList::class:
|
||||
$type = Type::listOf(self::param(
|
||||
$utopia,
|
||||
$validator->getValidator(),
|
||||
@@ -329,11 +329,11 @@ class Mapper
|
||||
$injections
|
||||
));
|
||||
break;
|
||||
case 'Utopia\Validator\Integer':
|
||||
case 'Utopia\Validator\Numeric':
|
||||
case \Utopia\Validator\Integer::class:
|
||||
case \Utopia\Validator\Numeric::class:
|
||||
$type = Type::int();
|
||||
break;
|
||||
case 'Utopia\Validator\Range':
|
||||
case \Utopia\Validator\Range::class:
|
||||
// Check if the Range validator is for float or integer
|
||||
if ($validator instanceof \Utopia\Validator\Range && $validator->getType() === \Utopia\Validator\Range::TYPE_FLOAT) {
|
||||
$type = Type::float();
|
||||
@@ -341,16 +341,16 @@ class Mapper
|
||||
$type = Type::int();
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\FloatValidator':
|
||||
case \Utopia\Validator\FloatValidator::class:
|
||||
$type = Type::float();
|
||||
break;
|
||||
case 'Utopia\Validator\Assoc':
|
||||
case \Utopia\Validator\Assoc::class:
|
||||
$type = Types::assoc();
|
||||
break;
|
||||
case 'Utopia\Validator\JSON':
|
||||
case \Utopia\Validator\JSON::class:
|
||||
$type = Types::json();
|
||||
break;
|
||||
case 'Utopia\Storage\Validator\File':
|
||||
case \Utopia\Storage\Validator\File::class:
|
||||
$type = Types::inputFile();
|
||||
break;
|
||||
}
|
||||
|
||||
+30
@@ -6,6 +6,7 @@ use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\EventProcessor;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Action as DatabasesAction;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
@@ -250,6 +251,35 @@ abstract class Action extends DatabasesAction
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate relationship values.
|
||||
* Handles Document objects, ID strings, and associative arrays.
|
||||
*/
|
||||
protected function validateRelationship(mixed $relation): void
|
||||
{
|
||||
$relationId = null;
|
||||
|
||||
if ($relation instanceof Document) {
|
||||
$relationId = $relation->getId();
|
||||
} elseif (\is_string($relation)) {
|
||||
$relationId = $relation;
|
||||
} elseif (\is_array($relation) && !\array_is_list($relation)) {
|
||||
$relationId = $relation['$id'] ?? null;
|
||||
} else {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, 'Relationship value must be an object, document ID string, or associative array');
|
||||
}
|
||||
|
||||
if ($relationId !== null) {
|
||||
if (!\is_string($relationId)) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, 'Relationship $id must be a string');
|
||||
}
|
||||
$validator = new CustomId();
|
||||
if (!$validator->isValid($relationId)) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves relationships in a document and attaches metadata.
|
||||
*/
|
||||
|
||||
+3
@@ -319,6 +319,9 @@ class Create extends Action
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
|
||||
$this->validateRelationship($relation);
|
||||
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
|
||||
+3
@@ -210,6 +210,9 @@ class Update extends Action
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
|
||||
$this->validateRelationship($relation);
|
||||
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
|
||||
+3
@@ -220,6 +220,9 @@ class Upsert extends Action
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
|
||||
$this->validateRelationship($relation);
|
||||
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class Create extends Base
|
||||
->param('logging', true, new Boolean(), 'When disabled, executions will exclude logs and errors, and will be slightly faster.', true)
|
||||
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
|
||||
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
|
||||
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
|
||||
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function.', true)
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
|
||||
|
||||
@@ -83,7 +83,7 @@ class Update extends Base
|
||||
->param('logging', true, new Boolean(), 'When disabled, executions will exclude logs and errors, and will be slightly faster.', true)
|
||||
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
|
||||
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
|
||||
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
|
||||
->param('providerRepositoryId', null, new Nullable(new Text(128, 0)), 'Repository ID of the repo linked to the function', true)
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
|
||||
|
||||
@@ -12,6 +12,7 @@ use Appwrite\SDK\Language\Flutter;
|
||||
use Appwrite\SDK\Language\Go;
|
||||
use Appwrite\SDK\Language\GraphQL;
|
||||
use Appwrite\SDK\Language\Kotlin;
|
||||
use Appwrite\SDK\Language\Markdown;
|
||||
use Appwrite\SDK\Language\Node;
|
||||
use Appwrite\SDK\Language\PHP;
|
||||
use Appwrite\SDK\Language\Python;
|
||||
@@ -31,6 +32,27 @@ use Utopia\Validator\WhiteList;
|
||||
|
||||
class SDKs extends Action
|
||||
{
|
||||
protected array $supportedSDKS = [
|
||||
'web',
|
||||
'cli',
|
||||
'php',
|
||||
'nodejs',
|
||||
'deno',
|
||||
'python',
|
||||
'ruby',
|
||||
'flutter',
|
||||
'react-native',
|
||||
'dart',
|
||||
'go',
|
||||
'swift',
|
||||
'apple',
|
||||
'dotnet',
|
||||
'android',
|
||||
'graphql',
|
||||
'rest',
|
||||
'markdown',
|
||||
];
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'sdks';
|
||||
@@ -61,6 +83,9 @@ class SDKs extends Action
|
||||
if (!$sdks) {
|
||||
$selectedPlatform ??= Console::confirm('Choose Platform ("' . implode('", "', static::getPlatforms()) . '" or "*" for all):');
|
||||
$selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
if ($selectedSDK !== '*' && !\in_array($selectedSDK, $this->supportedSDKS)) {
|
||||
throw new \Exception('Unknown SDK "' . $selectedSDK . '" given. Options are: ' . implode(', ', $this->supportedSDKS));
|
||||
}
|
||||
} else {
|
||||
$sdks = explode(',', $sdks);
|
||||
}
|
||||
@@ -252,6 +277,10 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
case 'rest':
|
||||
$config = new REST();
|
||||
break;
|
||||
case 'markdown':
|
||||
$config = new Markdown();
|
||||
$config->setNPMPackage('@appwrite.io/docs');
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('Language "' . $language['key'] . '" not supported');
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ class Screenshot extends Action
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'name' => 'Screenshot API key',
|
||||
'scopes' => \array_keys(Config::getParam('scopes', []))
|
||||
'scopes' => \array_keys(Config::getParam('projectScopes', []))
|
||||
]);
|
||||
|
||||
if ($response['headers']['status-code'] !== 201) {
|
||||
|
||||
@@ -276,7 +276,7 @@ class Certificates extends Action
|
||||
]));
|
||||
|
||||
// Rule not found (or) not in the expected state
|
||||
if ($rule->isEmpty() || $rule->getAttribute('status') !== RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
if ($rule->isEmpty() || !\in_array($rule->getAttribute('status'), [RULE_STATUS_CERTIFICATE_GENERATING, RULE_STATUS_VERIFIED])) {
|
||||
Console::warning('Certificate generation for ' . $domain->get() . ' is skipped as the associated rule is either empty or not in the expected state.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,9 @@ class Deletes extends Action
|
||||
case DELETE_TYPE_USERS:
|
||||
$this->deleteUser($getProjectDB, $document, $project);
|
||||
break;
|
||||
case DELETE_TYPE_TEAMS:
|
||||
$this->deleteTeam($getProjectDB, $document, $project);
|
||||
break;
|
||||
case DELETE_TYPE_BUCKETS:
|
||||
$this->deleteBucket($getProjectDB, $deviceForFiles, $document, $project);
|
||||
break;
|
||||
@@ -530,7 +533,7 @@ class Deletes extends Action
|
||||
}
|
||||
|
||||
/**
|
||||
* @var $dbForProject Database
|
||||
* @var Database $dbForProject
|
||||
*/
|
||||
$dbForProject = $getProjectDB($document);
|
||||
|
||||
@@ -661,6 +664,24 @@ class Deletes extends Action
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteTeam(callable $getProjectDB, Document $document, Document $project): void
|
||||
{
|
||||
$teamId = $document->getId();
|
||||
$teamInternalId = $document->getSequence();
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// Delete Keys
|
||||
$this->deleteByGroup('keys', [
|
||||
Query::equal('resourceInternalId', [$teamInternalId]),
|
||||
Query::equal('resourceType', ['teams']),
|
||||
Query::orderAsc()
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('teams', $teamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $document user document
|
||||
@@ -680,6 +701,15 @@ class Deletes extends Action
|
||||
Query::orderAsc()
|
||||
], $dbForProject);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// Delete Keys
|
||||
$this->deleteByGroup('keys', [
|
||||
Query::equal('resourceInternalId', [$userInternalId]),
|
||||
Query::equal('resourceType', ['users']),
|
||||
Query::orderAsc()
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
|
||||
// Delete Memberships and decrement team membership counts
|
||||
|
||||
@@ -392,51 +392,51 @@ class OpenAPI3 extends Format
|
||||
: '';
|
||||
|
||||
switch ($base) {
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Base::class:
|
||||
$class = $base;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($class === 'Utopia\Validator\AnyOf') {
|
||||
if ($class === \Utopia\Validator\AnyOf::class) {
|
||||
$validator = $param['validator']->getValidators()[0];
|
||||
$class = \get_class($validator);
|
||||
}
|
||||
|
||||
$array = false;
|
||||
if ($class === 'Utopia\Validator\ArrayList') {
|
||||
if ($class === \Utopia\Validator\ArrayList::class) {
|
||||
$array = true;
|
||||
$subclass = \get_class($validator->getValidator());
|
||||
switch ($subclass) {
|
||||
case 'Appwrite\Utopia\Database\Validator\Operation':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
case \Appwrite\Utopia\Database\Validator\Operation::class:
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
$class = $subclass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Utopia\Validator\Text':
|
||||
case \Utopia\Database\Validator\UID::class:
|
||||
case \Utopia\Validator\Text::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
|
||||
break;
|
||||
case 'Utopia\Validator\Boolean':
|
||||
case \Utopia\Validator\Boolean::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: false;
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case \Appwrite\Utopia\Database\Validator\CustomId::class:
|
||||
if ($sdk->getType() === MethodType::UPLOAD) {
|
||||
$node['schema']['x-upload-id'] = true;
|
||||
}
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\DatetimeValidator':
|
||||
case \Utopia\Database\Validator\DatetimeValidator::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'datetime';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: Model::TYPE_DATETIME_EXAMPLE;
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Spatial':
|
||||
case \Utopia\Database\Validator\Spatial::class:
|
||||
/** @var Spatial $validator */
|
||||
$node['schema']['type'] = 'array';
|
||||
$node['schema']['items'] = [
|
||||
@@ -450,31 +450,31 @@ class OpenAPI3 extends Format
|
||||
Database::VAR_POLYGON => '[[[1, 2], [3, 4], [5, 6], [1, 2]]]',
|
||||
};
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case \Appwrite\Network\Validator\Email::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'email';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: 'email@example.com';
|
||||
break;
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\URL':
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case \Utopia\Validator\Host::class:
|
||||
case \Utopia\Validator\URL::class:
|
||||
case \Appwrite\Network\Validator\Redirect::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'url';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: 'https://example.com';
|
||||
break;
|
||||
case 'Utopia\Validator\JSON':
|
||||
case 'Utopia\Validator\Mock':
|
||||
case 'Utopia\Validator\Assoc':
|
||||
case \Utopia\Validator\JSON::class:
|
||||
case \Utopia\Validator\Mock::class:
|
||||
case \Utopia\Validator\Assoc::class:
|
||||
$param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||
$node['schema']['type'] = 'object';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '{}';
|
||||
break;
|
||||
case 'Utopia\Storage\Validator\File':
|
||||
case \Utopia\Storage\Validator\File::class:
|
||||
$consumes = ['multipart/form-data'];
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'binary';
|
||||
break;
|
||||
case 'Utopia\Validator\ArrayList':
|
||||
case \Utopia\Validator\ArrayList::class:
|
||||
/** @var ArrayList $validator */
|
||||
$node['schema']['type'] = 'array';
|
||||
$node['schema']['items'] = [
|
||||
@@ -484,92 +484,92 @@ class OpenAPI3 extends Format
|
||||
$node['schema']['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Columns':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Tables':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Identities':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Messages':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Migrations':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Providers':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Subscribers':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Targets':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Topics':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
|
||||
case 'Utopia\Database\Validator\Queries':
|
||||
case 'Utopia\Database\Validator\Queries\Document':
|
||||
case 'Utopia\Database\Validator\Queries\Documents':
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Base::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Columns::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Attributes::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Buckets::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Tables::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Collections::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Databases::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Deployments::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Executions::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Files::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Functions::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Identities::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Indexes::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Installations::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Memberships::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Messages::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Migrations::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Projects::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Providers::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Rules::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Subscribers::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Targets::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Teams::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Topics::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Users::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Variables::class:
|
||||
case \Utopia\Database\Validator\Queries::class:
|
||||
case \Utopia\Database\Validator\Queries\Document::class:
|
||||
case \Utopia\Database\Validator\Queries\Documents::class:
|
||||
$node['schema']['type'] = 'array';
|
||||
$node['schema']['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Permissions':
|
||||
case \Utopia\Database\Validator\Permissions::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '["' . Permission::read(Role::any()) . '"]';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
case \Utopia\Database\Validator\Roles::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '["' . Role::any()->toString() . '"]';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case \Appwrite\Auth\Validator\Password::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'password';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: 'password';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Phone':
|
||||
case \Appwrite\Auth\Validator\Phone::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'phone';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '+12065550100'; // In the US, 555 is reserved like example.com
|
||||
break;
|
||||
case 'Utopia\Validator\Range':
|
||||
case \Utopia\Validator\Range::class:
|
||||
/** @var Range $validator */
|
||||
$node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
|
||||
$node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: $validator->getMin();
|
||||
break;
|
||||
case 'Utopia\Validator\Integer':
|
||||
case \Utopia\Validator\Integer::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = $validator->getFormat();
|
||||
if (!empty($param['example'])) {
|
||||
$node['schema']['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\Numeric':
|
||||
case 'Utopia\Validator\FloatValidator':
|
||||
case \Utopia\Validator\Numeric::class:
|
||||
case \Utopia\Validator\FloatValidator::class:
|
||||
$node['schema']['type'] = 'number';
|
||||
$node['schema']['format'] = 'float';
|
||||
if (!empty($param['example'])) {
|
||||
$node['schema']['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\Length':
|
||||
case \Utopia\Validator\Length::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
if (!empty($param['example'])) {
|
||||
$node['schema']['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
if ($array) {
|
||||
$validator = $validator->getValidator();
|
||||
|
||||
@@ -687,11 +687,11 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\CompoundUID':
|
||||
case \Appwrite\Utopia\Database\Validator\CompoundUID::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '<ID1:ID2>';
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\Operation':
|
||||
case \Appwrite\Utopia\Database\Validator\Operation::class:
|
||||
if ($array) {
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
@@ -397,51 +397,51 @@ class Swagger2 extends Format
|
||||
: '';
|
||||
|
||||
switch ($base) {
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Base::class:
|
||||
$class = $base;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($class === 'Utopia\Validator\AnyOf') {
|
||||
if ($class === \Utopia\Validator\AnyOf::class) {
|
||||
$validator = $param['validator']->getValidators()[0];
|
||||
$class = \get_class($validator);
|
||||
}
|
||||
|
||||
$array = false;
|
||||
if ($class === 'Utopia\Validator\ArrayList') {
|
||||
if ($class === \Utopia\Validator\ArrayList::class) {
|
||||
$array = true;
|
||||
$subclass = \get_class($validator->getValidator());
|
||||
switch ($subclass) {
|
||||
case 'Appwrite\Utopia\Database\Validator\Operation':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
case \Appwrite\Utopia\Database\Validator\Operation::class:
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
$class = $subclass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($class) {
|
||||
case 'Utopia\Validator\Text':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case \Utopia\Validator\Text::class:
|
||||
case \Utopia\Database\Validator\UID::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
|
||||
break;
|
||||
case 'Utopia\Validator\Boolean':
|
||||
case \Utopia\Validator\Boolean::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: false;
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case \Appwrite\Utopia\Database\Validator\CustomId::class:
|
||||
if ($sdk->getType() === MethodType::UPLOAD) {
|
||||
$node['x-upload-id'] = true;
|
||||
}
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\DatetimeValidator':
|
||||
case \Utopia\Database\Validator\DatetimeValidator::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'datetime';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: Model::TYPE_DATETIME_EXAMPLE;
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Spatial':
|
||||
case \Utopia\Database\Validator\Spatial::class:
|
||||
/** @var Spatial $validator */
|
||||
$node['type'] = 'array';
|
||||
$node['schema']['items'] = [
|
||||
@@ -455,19 +455,19 @@ class Swagger2 extends Format
|
||||
Database::VAR_POLYGON => '[[[1, 2], [3, 4], [5, 6], [1, 2]]]',
|
||||
};
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case \Appwrite\Network\Validator\Email::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'email';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: 'email@example.com';
|
||||
break;
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\URL':
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case \Utopia\Validator\Host::class:
|
||||
case \Utopia\Validator\URL::class:
|
||||
case \Appwrite\Network\Validator\Redirect::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'url';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: 'https://example.com';
|
||||
break;
|
||||
case 'Utopia\Validator\ArrayList':
|
||||
case \Utopia\Validator\ArrayList::class:
|
||||
/** @var ArrayList $validator */
|
||||
$node['type'] = 'array';
|
||||
$node['collectionFormat'] = 'multi';
|
||||
@@ -478,34 +478,34 @@ class Swagger2 extends Format
|
||||
$node['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\JSON':
|
||||
case 'Utopia\Validator\Mock':
|
||||
case 'Utopia\Validator\Assoc':
|
||||
case \Utopia\Validator\JSON::class:
|
||||
case \Utopia\Validator\Mock::class:
|
||||
case \Utopia\Validator\Assoc::class:
|
||||
$node['type'] = 'object';
|
||||
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '{}';
|
||||
break;
|
||||
case 'Utopia\Storage\Validator\File':
|
||||
case \Utopia\Storage\Validator\File::class:
|
||||
$consumes = ['multipart/form-data'];
|
||||
$node['type'] = 'file';
|
||||
break;
|
||||
case 'Appwrite\Functions\Validator\Payload':
|
||||
case \Appwrite\Functions\Validator\Payload::class:
|
||||
$consumes = ['multipart/form-data'];
|
||||
$node['type'] = 'payload';
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case 'Utopia\Database\Validator\Queries':
|
||||
case 'Utopia\Database\Validator\Queries\Document':
|
||||
case 'Utopia\Database\Validator\Queries\Documents':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Columns':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Tables':
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Base::class:
|
||||
case \Utopia\Database\Validator\Queries::class:
|
||||
case \Utopia\Database\Validator\Queries\Document::class:
|
||||
case \Utopia\Database\Validator\Queries\Documents::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Columns::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Tables::class:
|
||||
$node['type'] = 'array';
|
||||
$node['collectionFormat'] = 'multi';
|
||||
$node['items'] = [
|
||||
'type' => 'string',
|
||||
];
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Permissions':
|
||||
case \Utopia\Database\Validator\Permissions::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['collectionFormat'] = 'multi';
|
||||
$node['items'] = [
|
||||
@@ -513,7 +513,7 @@ class Swagger2 extends Format
|
||||
];
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '["' . Permission::read(Role::any()) . '"]';
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
case \Utopia\Database\Validator\Roles::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['collectionFormat'] = 'multi';
|
||||
$node['items'] = [
|
||||
@@ -521,44 +521,44 @@ class Swagger2 extends Format
|
||||
];
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '["' . Role::any()->toString() . '"]';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case \Appwrite\Auth\Validator\Password::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'password';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: 'password';
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Phone':
|
||||
case \Appwrite\Auth\Validator\Phone::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'phone';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '+12065550100';
|
||||
break;
|
||||
case 'Utopia\Validator\Range':
|
||||
case \Utopia\Validator\Range::class:
|
||||
/** @var Range $validator */
|
||||
$node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
|
||||
$node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: $validator->getMin();
|
||||
break;
|
||||
case 'Utopia\Validator\Integer':
|
||||
case \Utopia\Validator\Integer::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = $validator->getFormat();
|
||||
if (!empty($param['example'])) {
|
||||
$node['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\Numeric':
|
||||
case 'Utopia\Validator\FloatValidator':
|
||||
case \Utopia\Validator\Numeric::class:
|
||||
case \Utopia\Validator\FloatValidator::class:
|
||||
$node['type'] = 'number';
|
||||
$node['format'] = 'float';
|
||||
if (!empty($param['example'])) {
|
||||
$node['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\Length':
|
||||
case \Utopia\Validator\Length::class:
|
||||
$node['type'] = $validator->getType();
|
||||
if (!empty($param['example'])) {
|
||||
$node['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
if ($array) {
|
||||
$validator = $validator->getValidator();
|
||||
|
||||
@@ -665,11 +665,11 @@ class Swagger2 extends Format
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\CompoundUID':
|
||||
case \Appwrite\Utopia\Database\Validator\CompoundUID::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '<ID1:ID2>';
|
||||
break;
|
||||
case 'Appwrite\Utopia\Database\Validator\Operation':
|
||||
case \Appwrite\Utopia\Database\Validator\Operation::class:
|
||||
if ($array) {
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class Key extends Model
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
protected bool $public = true; // Public because reused for more key types
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -7626,6 +7626,267 @@ trait DatabasesBase
|
||||
$this->assertEquals(200, $update['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDatabase
|
||||
*/
|
||||
public function testInvalidRelationshipDocumentId(array $data): void
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
// Create parent table
|
||||
$parentTable = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'tableId' => ID::unique(),
|
||||
'name' => 'ParentTable',
|
||||
]);
|
||||
$this->assertEquals(201, $parentTable['headers']['status-code']);
|
||||
$parentTableId = $parentTable['body']['$id'];
|
||||
|
||||
// Create child table
|
||||
$childTable = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'tableId' => ID::unique(),
|
||||
'name' => 'ChildTable',
|
||||
]);
|
||||
$this->assertEquals(201, $childTable['headers']['status-code']);
|
||||
$childTableId = $childTable['body']['$id'];
|
||||
|
||||
// Add string column to parent
|
||||
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/columns/string', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'key' => 'name',
|
||||
'size' => 255,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
// Add string column to child
|
||||
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $childTableId . '/columns/string', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'key' => 'title',
|
||||
'size' => 255,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
// Create one-to-many relationship
|
||||
$relationship = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/columns/relationship', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'relatedTableId' => $childTableId,
|
||||
'type' => Database::RELATION_ONE_TO_MANY,
|
||||
'twoWay' => false,
|
||||
'key' => 'children',
|
||||
]);
|
||||
$this->assertEquals(202, $relationship['headers']['status-code']);
|
||||
|
||||
// Wait for relationship column to be available
|
||||
$this->assertEventually(function () use ($databaseId, $parentTableId) {
|
||||
$columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/columns', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]));
|
||||
$columnKeys = array_column($columns['body']['columns'], 'key');
|
||||
$this->assertContains('children', $columnKeys, "Relationship column 'children' not found in table {$parentTableId} of database {$databaseId}");
|
||||
}, 2000, 200);
|
||||
|
||||
// ID too long (>36 chars) should fail
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 1',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => 'this_id_is_way_too_long_and_should_fail_validation_check',
|
||||
'title' => 'Child 1',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// ID with invalid characters should fail
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 2',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => 'invalid@id#with$special%chars',
|
||||
'title' => 'Child 2',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// ID starting with underscore should fail
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 3',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => '_startsWithUnderscore',
|
||||
'title' => 'Child 3',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Valid ID should succeed
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 4',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => 'valid-id-123',
|
||||
'title' => 'Child 4',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$parentRowId = $response['body']['$id'];
|
||||
|
||||
// Update with invalid relationship ID should fail
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows/' . $parentRowId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'data' => [
|
||||
'children' => [
|
||||
[
|
||||
'$id' => 'another@invalid#id',
|
||||
'title' => 'Child 5',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Invalid string relation ID should fail
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 6',
|
||||
'children' => [
|
||||
'invalid@string#id',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Integer as relation value should fail
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 7',
|
||||
'children' => [
|
||||
12345,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// unique() as $id should succeed
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 8',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => 'unique()',
|
||||
'title' => 'Child 8',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Empty string as $id should fail
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 9',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => '',
|
||||
'title' => 'Child 9',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Valid ID with allowed special chars (hyphen, period) should succeed
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $parentTableId . '/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'rowId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Parent 10',
|
||||
'children' => [
|
||||
[
|
||||
'$id' => 'valid.id-with_chars',
|
||||
'title' => 'Child 10',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDatabase
|
||||
*/
|
||||
|
||||
@@ -1765,12 +1765,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateProjectAuthLimit
|
||||
*/
|
||||
public function testUpdateProjectAuthSessionsLimit($data): array
|
||||
public function testUpdateProjectAuthSessionsLimit(): void
|
||||
{
|
||||
$id = $data['projectId'] ?? '';
|
||||
$id = $this->setupProject([
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'testUpdateProjectAuthSessionsLimit',
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
|
||||
/**
|
||||
* Test for failure
|
||||
@@ -1881,10 +1882,9 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'limit' => 10,
|
||||
]);
|
||||
|
||||
return $data;
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @depends testUpdateProjectAuthLimit
|
||||
*/
|
||||
|
||||
+317
-5
@@ -14,6 +14,7 @@ class KeyTest extends TestCase
|
||||
{
|
||||
public function testDecode(): void
|
||||
{
|
||||
// Decode dynamic key
|
||||
$projectId = 'test';
|
||||
$usage = false;
|
||||
$scopes = [
|
||||
@@ -22,34 +23,345 @@ class KeyTest extends TestCase
|
||||
'documents.read',
|
||||
];
|
||||
$roleScopes = Config::getParam('roles', [])[User::ROLE_APPS]['scopes'];
|
||||
$guestRoleScopes = Config::getParam('roles', [])[User::ROLE_GUESTS]['scopes'];
|
||||
|
||||
$key = static::generateKey($projectId, $usage, $scopes);
|
||||
$project = new Document(['$id' => $projectId,]);
|
||||
$decoded = Key::decode($project, $key);
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: $key,
|
||||
);
|
||||
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_DYNAMIC, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes());
|
||||
$this->assertEquals('Dynamic Key', $decoded->getName());
|
||||
|
||||
// Decode dynamic key with extras
|
||||
$extra = [
|
||||
'disabledMetrics' => ['metric123'],
|
||||
'hostnameOverride' => true,
|
||||
'bannerDisabled' => true,
|
||||
'projectCheckDisabled' => true,
|
||||
'previewAuthDisabled' => true,
|
||||
'deploymentStatusIgnored' => true,
|
||||
];
|
||||
$key = static::generateKey($projectId, $usage, $scopes, extra: $extra);
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: $key,
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_DYNAMIC, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes());
|
||||
$this->assertEquals('Dynamic Key', $decoded->getName());
|
||||
$this->assertEquals(['metric123'], $decoded->getDisabledMetrics());
|
||||
$this->assertEquals(true, $decoded->getHostnameOverride());
|
||||
$this->assertEquals(true, $decoded->isBannerDisabled());
|
||||
$this->assertEquals(true, $decoded->isProjectCheckDisabled());
|
||||
$this->assertEquals(true, $decoded->isPreviewAuthDisabled());
|
||||
$this->assertEquals(true, $decoded->isDeploymentStatusIgnored());
|
||||
|
||||
// Decode invalid dynamic key
|
||||
$invalidKey = API_KEY_DYNAMIC . '_invalid_jwt_token';
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: $invalidKey,
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_DYNAMIC, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_GUESTS, $decoded->getRole());
|
||||
$this->assertEquals($guestRoleScopes, $decoded->getScopes());
|
||||
$this->assertEquals('UNKNOWN', $decoded->getName());
|
||||
|
||||
// Decode expired dynamic key
|
||||
$expiredKey = static::generateKey($projectId, $usage, $scopes, maxAge: 1, timestamp: time() - 60);
|
||||
\sleep(2);
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: $expiredKey,
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_DYNAMIC, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_GUESTS, $decoded->getRole());
|
||||
$this->assertEquals($guestRoleScopes, $decoded->getScopes());
|
||||
$this->assertEquals('UNKNOWN', $decoded->getName());
|
||||
|
||||
// Decode standard key
|
||||
$scopes = ['custom.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'standard_abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Standard key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: 'standard_abcd1234',
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_STANDARD, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes());
|
||||
$this->assertEquals('Standard key', $decoded->getName());
|
||||
|
||||
// Decode deprecated standard key
|
||||
$scopes = ['custom.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Standard key',
|
||||
'scopes' => ['custom.write']
|
||||
])
|
||||
]]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: 'abcd1234',
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_STANDARD, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes());
|
||||
$this->assertEquals('Standard key', $decoded->getName());
|
||||
|
||||
// Decode invalid standard key
|
||||
$scopes = ['custom.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'standard_abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Standard key',
|
||||
'scopes' => ['custom.write']
|
||||
])
|
||||
]]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: 'standard_efgh5678',
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_STANDARD, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_GUESTS, $decoded->getRole());
|
||||
$this->assertEquals($guestRoleScopes, $decoded->getScopes());
|
||||
$this->assertEquals('UNKNOWN', $decoded->getName());
|
||||
|
||||
// Decode expired standard key
|
||||
$scopes = ['custom.write'];
|
||||
$yesterday = (new \DateTimeImmutable('-1 day'))->format('Y-m-d\TH:i:s\Z');
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'standard_abcd1234',
|
||||
'expire' => $yesterday,
|
||||
'name' => 'Standard key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
team: new Document(),
|
||||
user: new Document(),
|
||||
key: 'standard_abcd1234',
|
||||
);
|
||||
$this->assertEquals(true, $decoded->isExpired());
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_STANDARD, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals(\array_merge($scopes, $roleScopes), $decoded->getScopes());
|
||||
$this->assertEquals('Standard key', $decoded->getName());
|
||||
|
||||
// Decode account key
|
||||
$userId = 'user123';
|
||||
$scopes = ['teams.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(['$id' => $userId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'account_abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Account key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
key: 'account_abcd1234',
|
||||
);
|
||||
$this->assertEquals('', $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals($userId, $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_ACCOUNT, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_USERS, $decoded->getRole());
|
||||
$this->assertEquals($scopes, $decoded->getScopes());
|
||||
$this->assertEquals('Account key', $decoded->getName());
|
||||
|
||||
// Decode invalid account key
|
||||
$scopes = ['teams.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(['$id' => $userId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'account_abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Account key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
key: 'account_efgh5678',
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_ACCOUNT, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_GUESTS, $decoded->getRole());
|
||||
$this->assertEquals($guestRoleScopes, $decoded->getScopes());
|
||||
$this->assertEquals('UNKNOWN', $decoded->getName());
|
||||
|
||||
// Decode expired account key
|
||||
$scopes = ['teams.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
user: new Document(['$id' => $userId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'account_abcd1234',
|
||||
'expire' => $yesterday,
|
||||
'name' => 'Account key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
key: 'account_abcd1234',
|
||||
);
|
||||
$this->assertEquals(true, $decoded->isExpired());
|
||||
$this->assertEquals('', $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals($userId, $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_ACCOUNT, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_USERS, $decoded->getRole());
|
||||
$this->assertEquals($scopes, $decoded->getScopes());
|
||||
$this->assertEquals('Account key', $decoded->getName());
|
||||
|
||||
// Decode organization key
|
||||
$teamId = 'team123';
|
||||
$scopes = ['projects.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(['$id' => $teamId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'organization_abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Organization key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
user: new Document(),
|
||||
key: 'organization_abcd1234',
|
||||
);
|
||||
$this->assertEquals('', $decoded->getProjectId());
|
||||
$this->assertEquals($teamId, $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_ORGANIZATION, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals($scopes, $decoded->getScopes());
|
||||
$this->assertEquals('Organization key', $decoded->getName());
|
||||
|
||||
// Decode invalid organization key
|
||||
$scopes = ['projects.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(['$id' => $teamId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'organization_abcd1234',
|
||||
'expire' => null,
|
||||
'name' => 'Organization key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
user: new Document(),
|
||||
key: 'organization_efgh5678',
|
||||
);
|
||||
$this->assertEquals($projectId, $decoded->getProjectId());
|
||||
$this->assertEquals('', $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_ORGANIZATION, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_GUESTS, $decoded->getRole());
|
||||
$this->assertEquals($guestRoleScopes, $decoded->getScopes());
|
||||
$this->assertEquals('UNKNOWN', $decoded->getName());
|
||||
|
||||
// Decode expired organization key
|
||||
$scopes = ['projects.write'];
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(['$id' => $teamId, 'keys' => [
|
||||
new Document([
|
||||
'secret' => 'organization_abcd1234',
|
||||
'expire' => $yesterday,
|
||||
'name' => 'Organization key',
|
||||
'scopes' => $scopes
|
||||
])
|
||||
]]),
|
||||
user: new Document(),
|
||||
key: 'organization_abcd1234',
|
||||
);
|
||||
$this->assertEquals(true, $decoded->isExpired());
|
||||
$this->assertEquals('', $decoded->getProjectId());
|
||||
$this->assertEquals($teamId, $decoded->getTeamId());
|
||||
$this->assertEquals('', $decoded->getUserId());
|
||||
$this->assertEquals(API_KEY_ORGANIZATION, $decoded->getType());
|
||||
$this->assertEquals(User::ROLE_APPS, $decoded->getRole());
|
||||
$this->assertEquals($scopes, $decoded->getScopes());
|
||||
$this->assertEquals('Organization key', $decoded->getName());
|
||||
}
|
||||
|
||||
private static function generateKey(
|
||||
string $projectId,
|
||||
bool $usage,
|
||||
array $scopes,
|
||||
int $maxAge = 86400,
|
||||
?int $timestamp = null,
|
||||
array $extra = []
|
||||
): string {
|
||||
$jwt = new JWT(
|
||||
key: System::getEnv('_APP_OPENSSL_KEY_V1'),
|
||||
algo: 'HS256',
|
||||
maxAge: 86400,
|
||||
maxAge: $maxAge,
|
||||
leeway: 0,
|
||||
);
|
||||
$jwt->setTestTimestamp($timestamp);
|
||||
|
||||
$apiKey = $jwt->encode([
|
||||
$apiKey = $jwt->encode(\array_merge([
|
||||
'projectId' => $projectId,
|
||||
'usage' => $usage,
|
||||
'scopes' => $scopes,
|
||||
]);
|
||||
], $extra));
|
||||
|
||||
return API_KEY_DYNAMIC . '_' . $apiKey;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user