Merge branch '0.16.x' into feat-variables-api
This commit is contained in:
commit
0897cd0fe4
|
@ -1003,6 +1003,28 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('accessedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('sdks'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
@ -1012,6 +1034,13 @@ $collections = [
|
|||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['accessedAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -121,6 +121,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'etsy' => [
|
||||
'name' => 'Etsy',
|
||||
'developers' => 'https://developers.etsy.com/',
|
||||
'icon' => 'icon-etsy',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'facebook' => [
|
||||
'name' => 'Facebook',
|
||||
'developers' => 'https://developers.facebook.com/',
|
||||
|
|
|
@ -59,9 +59,12 @@ return [
|
|||
'home',
|
||||
'console',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'execution.write',
|
||||
],
|
||||
],
|
||||
Auth::USER_ROLE_USERS => [
|
||||
|
|
|
@ -1358,7 +1358,7 @@ App::get('/v1/account/logs')
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new EventAudit($dbForProject);
|
||||
|
|
|
@ -36,17 +36,12 @@ use Appwrite\Network\Validator\Email;
|
|||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor as Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter as Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit as Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset as Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order as Order;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Stats\Stats;
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Collections;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Databases;
|
||||
|
@ -250,12 +245,10 @@ App::get('/v1/databases')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$databaseId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
@ -328,7 +321,7 @@ App::get('/v1/databases/:databaseId/logs')
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
@ -573,12 +566,10 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$collectionId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
|
@ -670,7 +661,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
@ -1822,6 +1813,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'documents.{scope}.requests.create')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}'])
|
||||
->label('abuse-limit', 120)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'createDocument')
|
||||
|
@ -1987,12 +1980,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$documentId = $cursor->getValue();
|
||||
|
||||
|
@ -2135,7 +2126,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
@ -2201,6 +2192,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}')
|
||||
->label('usage.metric', 'documents.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateDocument')
|
||||
|
@ -2330,6 +2323,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}')
|
||||
->label('usage.metric', 'documents.{scope}.requests.delete')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'deleteDocument')
|
||||
|
|
|
@ -117,12 +117,10 @@ App::get('/v1/functions')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$functionId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('functions', $functionId);
|
||||
|
@ -788,16 +786,14 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Set resource queries
|
||||
$queries[] = Query::equal('resourceId', [$function->getId()]);
|
||||
$queries[] = Query::equal('resourceType', ['functions']);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$deploymentId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
@ -1149,15 +1145,13 @@ App::get('/v1/functions/:functionId/executions')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Set internal queries
|
||||
$queries[] = Query::equal('functionId', [$function->getId()]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$executionId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('executions', $executionId);
|
||||
|
|
|
@ -865,6 +865,8 @@ App::post('/v1/projects/:projectId/keys')
|
|||
'name' => $name,
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
'sdks' => [],
|
||||
'accessedAt' => null,
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
|
|
|
@ -164,12 +164,10 @@ App::get('/v1/storage/buckets')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$bucketId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
@ -329,6 +327,8 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->label('audits.resource', 'files/{response.$id}')
|
||||
->label('usage.metric', 'files.{scope}.requests.create')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'createFile')
|
||||
|
@ -678,12 +678,10 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$fileId = $cursor->getValue();
|
||||
|
||||
|
@ -1209,6 +1207,8 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->label('audits.resource', 'files/{response.$id}')
|
||||
->label('usage.metric', 'files.{scope}.requests.update')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'updateFile')
|
||||
|
@ -1308,6 +1308,8 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->label('audits.resource', 'file/{request.fileId}')
|
||||
->label('usage.metric', 'files.{scope}.requests.delete')
|
||||
->label('usage.params', ['bucketId:{request.bucketId}'])
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'storage')
|
||||
->label('sdk.method', 'deleteFile')
|
||||
|
@ -1364,9 +1366,12 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->setResource('file/' . $fileId)
|
||||
;
|
||||
|
||||
// Don't need to check valid here because we already ensured validity
|
||||
if ($fileSecurity) {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
if ($fileSecurity && !$valid) {
|
||||
try {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
$deleted = Authorization::skip(fn() => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
|
|
@ -79,6 +79,10 @@ App::post('/v1/teams')
|
|||
])));
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
|
||||
if (!\in_array('owner', $roles)) {
|
||||
$roles[] = 'owner';
|
||||
}
|
||||
|
||||
$membershipId = ID::unique();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
|
@ -139,12 +143,10 @@ App::get('/v1/teams')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$teamId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('teams', $teamId);
|
||||
|
@ -484,15 +486,13 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Set internal queries
|
||||
$queries[] = Query::equal('teamId', [$teamId]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$membershipId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
@ -872,7 +872,7 @@ App::get('/v1/teams/:teamId/logs')
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
|
|
@ -355,12 +355,10 @@ App::get('/v1/users')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set default limit
|
||||
$queries[] = Query::limit(25);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null;
|
||||
if ($cursor !== null) {
|
||||
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
$userId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('users', $userId);
|
||||
|
@ -544,7 +542,7 @@ App::get('/v1/users/:userId/logs')
|
|||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
|
|
@ -32,6 +32,7 @@ use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
|
|||
use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
|
||||
use Appwrite\Utopia\Request\Filters\V14 as RequestV14;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
|
@ -47,7 +48,8 @@ App::init()
|
|||
->inject('user')
|
||||
->inject('locale')
|
||||
->inject('clients')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
|
||||
->inject('servers')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients, array $servers) {
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
|
@ -303,6 +305,28 @@ App::init()
|
|||
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
|
||||
$accessedAt = $key->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
|
||||
if ($sdkValidator->isValid($sdk)) {
|
||||
$sdks = $key->getAttribute('sdks', []);
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
array_push($sdks, $sdk);
|
||||
$key->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use Utopia\Database\Document;
|
|||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\App;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Integer;
|
||||
|
@ -395,6 +396,35 @@ App::get('/v1/mock/tests/general/empty')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
/** Endpoint to test if required headers are sent from the SDK */
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'headers')
|
||||
->label('sdk.description', 'Return headers from the request')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$res = [
|
||||
'x-sdk-name' => $request->getHeader('x-sdk-name'),
|
||||
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
|
||||
'x-sdk-language' => $request->getHeader('x-sdk-language'),
|
||||
'x-sdk-version' => $request->getHeader('x-sdk-version'),
|
||||
];
|
||||
$res = array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($res), $res);
|
||||
$res = implode("; ", $res);
|
||||
|
||||
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/400-error')
|
||||
->desc('400 Error')
|
||||
->groups(['mock'])
|
||||
|
|
|
@ -294,6 +294,7 @@ App::get('/console/databases/collection')
|
|||
$permissions
|
||||
->setParam('method', 'databases.getCollection')
|
||||
->setParam('events', 'load,databases.updateCollection')
|
||||
->setParam('form', 'collectionPermissions')
|
||||
->setParam('data', 'project-collection')
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.id}}',
|
||||
|
@ -329,7 +330,6 @@ App::get('/console/databases/document')
|
|||
->action(function (string $databaseId, string $collection, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'databases.listDocumentLogs')
|
||||
|
@ -341,15 +341,16 @@ App::get('/console/databases/document')
|
|||
;
|
||||
|
||||
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
|
||||
$permissions
|
||||
->setParam('method', 'databases.getDocument')
|
||||
->setParam('events', 'load,databases.updateDocument')
|
||||
->setParam('form', 'documentPermissions')
|
||||
->setParam('data', 'project-document')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
))
|
||||
->setParam('permissions', [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
])
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.collection}}',
|
||||
'database-id' => '{{router.params.databaseId}}',
|
||||
|
@ -384,10 +385,12 @@ App::get('/console/databases/document/new')
|
|||
|
||||
$permissions
|
||||
->setParam('data', 'project-document')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
))
|
||||
->setParam('form', 'documentPermissions')
|
||||
->setParam('permissions', [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
])
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.collection}}',
|
||||
'database-id' => '{{router.params.databaseId}}',
|
||||
|
@ -451,20 +454,22 @@ App::get('/console/storage/bucket')
|
|||
$fileCreatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
$fileCreatePermissions
|
||||
->setParam('form', 'fileCreatePermissions')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
));
|
||||
->setParam('permissions', [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
]);
|
||||
|
||||
$fileUpdatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
|
||||
$fileUpdatePermissions
|
||||
->setParam('method', 'storage.getFile')
|
||||
->setParam('data', 'file')
|
||||
->setParam('form', 'fileUpdatePermissions')
|
||||
->setParam('permissions', \array_filter(
|
||||
Database::PERMISSIONS,
|
||||
fn ($perm) => $perm != Database::PERMISSION_CREATE
|
||||
))
|
||||
->setParam('permissions', [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
])
|
||||
->setParam('params', [
|
||||
'bucket-id' => '{{router.params.id}}',
|
||||
]);
|
||||
|
|
12
app/init.php
12
app/init.php
|
@ -90,6 +90,7 @@ const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
|||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 402;
|
||||
const APP_VERSION_STABLE = '0.15.3';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
|
@ -1028,3 +1029,14 @@ App::setResource('sms', function () {
|
|||
default => null
|
||||
};
|
||||
});
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$server = $platforms[APP_PLATFORM_SERVER];
|
||||
|
||||
$languages = array_map(function ($language) {
|
||||
return strtolower($language['name']);
|
||||
}, $server['languages']);
|
||||
|
||||
return $languages;
|
||||
});
|
||||
|
|
|
@ -178,6 +178,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
->setLicense($license)
|
||||
->setLicenseContent($licenseContent)
|
||||
->setVersion($language['version'])
|
||||
->setPlatform($key)
|
||||
->setGitURL($language['url'])
|
||||
->setGitRepo($language['gitUrl'])
|
||||
->setGitRepoName($language['gitRepoName'])
|
||||
|
|
|
@ -7,7 +7,7 @@ $params = $this->getParam('params', []);
|
|||
$events = $this->getParam('events', '');
|
||||
$permissions = $this->getParam('permissions', Database::PERMISSIONS);
|
||||
$data = $this->getParam('data', '');
|
||||
$form = $this->getParam('form', 'form');
|
||||
$form = $this->getParam('form');
|
||||
|
||||
$escapedPermissions = \array_map(function ($perm) {
|
||||
// Alpine won't bind to a parameter named delete
|
||||
|
@ -34,7 +34,7 @@ $escapedPermissions = \array_map(function ($perm) {
|
|||
<?php if (!empty($data)): ?>
|
||||
data-name="<?php echo $data; ?>"
|
||||
<?php endif; ?>
|
||||
@reset.window="permissions = rawPermissions = []">
|
||||
@reset.window="permissions.length = rawPermissions.length = 0">
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
|
@ -79,7 +79,7 @@ $escapedPermissions = \array_map(function ($perm) {
|
|||
</tr>
|
||||
</template>
|
||||
<tr x-data="permissionsRow"
|
||||
@addrow.window="addPermission('<?php echo $form; ?>',role,{<?php echo \implode(',', $escapedPermissions) ?>})">
|
||||
@addrow<?php echo \strtolower($form); ?>.window="addPermission('<?php echo $form; ?>', role, { <?php echo \implode(',', $escapedPermissions) ?> })">
|
||||
<td>
|
||||
<datalist id="types">
|
||||
<option value="user:">
|
||||
|
@ -91,7 +91,7 @@ $escapedPermissions = \array_map(function ($perm) {
|
|||
|
||||
<input
|
||||
required
|
||||
id="<?php echo $form; ?>"
|
||||
id="<?php echo $form; ?>Input"
|
||||
name="<?php echo $form; ?>"
|
||||
form="<?php echo $form ?>"
|
||||
list="types"
|
||||
|
@ -109,7 +109,7 @@ $escapedPermissions = \array_map(function ($perm) {
|
|||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="<?php \count($permissions) + 2 ?>">
|
||||
<button type="button" class="btn btn-primary margin-top-small" @click="$dispatch('addrow')">Add</button>
|
||||
<button type="button" class="margin-top-small reverse" @click="$dispatch('addrow<?php echo \strtolower($form); ?>')">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
|
|
@ -514,7 +514,7 @@ $permissions = $this->getParam('permissions', null);
|
|||
|
||||
<div class="row responsive margin-top-negative">
|
||||
<div class="col span-8 margin-bottom">
|
||||
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
|
||||
<form id="<?php echo $permissions->getParam('form') ?>"></form>
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
|
|
|
@ -53,7 +53,7 @@ $permissions = $this->getParam('permissions', null);
|
|||
|
||||
<div class="row responsive">
|
||||
<div class="col span-8 margin-bottom">
|
||||
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
|
||||
<form id="<?php echo $permissions->getParam('form') ?>"></form>
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
|
|
|
@ -108,7 +108,7 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
|
|||
<td class="col-name" data-title="Name: " data-ls-attrs="title={{file.name}}" >
|
||||
<div class="trim-inner-text">
|
||||
<span data-ls-ui-trigger="modal-file-update-{{file.$id}}" class="link text-one-liner" data-ls-bind="{{file.name}}"></span>
|
||||
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal sticky-footer width-large close" >
|
||||
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal width-xlarge sticky-footer close" >
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h1>Update File</h1>
|
||||
|
@ -139,12 +139,11 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
|
|||
</div>
|
||||
<input type="hidden" data-ls-attrs="id=file-bucketId-{{file.$id}}" name="bucketId" data-ls-bind="{{file.bucketId}}">
|
||||
|
||||
<div class="toggle margin-bottom" data-ls-if="{{project-bucket.fileSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
|
||||
<i class="icon-plus pull-end margin-top-tiny"></i>
|
||||
<i class="icon-minus pull-end margin-top-tiny"></i>
|
||||
<hr class="margin-start margin-end" />
|
||||
|
||||
<h3 class="margin-bottom-large">Permissions</h3>
|
||||
<h3 class="margin-bottom">Permissions</h3>
|
||||
|
||||
<div class="margin-start margin-end margin-bottom">
|
||||
<?php echo $fileUpdatePermissions->render(); ?>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -77,8 +77,8 @@
|
|||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "dev-feat-new-headers",
|
||||
"ext-fileinfo": "*",
|
||||
"appwrite/sdk-generator": "dev-master as 0.19.5",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
|
|
26
composer.lock
generated
26
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1145ff29befcc4aa21b5002da0b8319c",
|
||||
"content-hash": "039de21eff3a27955696a9f6f645c548",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -2837,16 +2837,16 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "dev-master",
|
||||
"version": "dev-feat-new-headers",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "6597948263e88f73dbdd5c70259dd54aff2dfcf8"
|
||||
"reference": "6e630a62f522ac68a7056bebf81cd032c7a053ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6597948263e88f73dbdd5c70259dd54aff2dfcf8",
|
||||
"reference": "6597948263e88f73dbdd5c70259dd54aff2dfcf8",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6e630a62f522ac68a7056bebf81cd032c7a053ba",
|
||||
"reference": "6e630a62f522ac68a7056bebf81cd032c7a053ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2861,7 +2861,6 @@
|
|||
"brianium/paratest": "^6.4",
|
||||
"phpunit/phpunit": "^9.5.21"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -2882,9 +2881,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/master"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/feat-new-headers"
|
||||
},
|
||||
"time": "2022-08-30T18:29:13+00:00"
|
||||
"time": "2022-08-29T10:43:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
|
@ -5356,14 +5355,7 @@
|
|||
"time": "2022-08-12T06:47:24+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "appwrite/sdk-generator",
|
||||
"version": "9999999-dev",
|
||||
"alias": "0.19.5",
|
||||
"alias_normalized": "0.19.5.0"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"appwrite/sdk-generator": 20
|
||||
|
@ -5391,5 +5383,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "8.0"
|
||||
},
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.2.0"
|
||||
}
|
||||
|
|
6
public/dist/scripts/app-all.js
vendored
6
public/dist/scripts/app-all.js
vendored
|
@ -4071,11 +4071,13 @@ if(this.attribute){event+=`.${this.attribute}`;}
|
|||
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
|
||||
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
|
||||
if(existing===undefined){this.permissions.push({role,[type]:true,});}
|
||||
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
|
||||
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
|
||||
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
|
||||
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
|
||||
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
|
||||
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
|
||||
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
|
||||
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
|
||||
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
|
||||
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
|
||||
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
|
||||
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
|
||||
|
|
6
public/dist/scripts/app.js
vendored
6
public/dist/scripts/app.js
vendored
|
@ -609,11 +609,13 @@ if(this.attribute){event+=`.${this.attribute}`;}
|
|||
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
|
||||
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
|
||||
if(existing===undefined){this.permissions.push({role,[type]:true,});}
|
||||
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
|
||||
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
|
||||
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
|
||||
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
|
||||
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
|
||||
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
|
||||
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
|
||||
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
|
||||
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
|
||||
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
|
||||
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
|
||||
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
|
||||
|
|
2
public/dist/styles/default-ltr.css
vendored
2
public/dist/styles/default-ltr.css
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles/default-rtl.css
vendored
2
public/dist/styles/default-rtl.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/users/etsy.png
Normal file
BIN
public/images/users/etsy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
|
@ -34,7 +34,7 @@
|
|||
});
|
||||
},
|
||||
addPermission(formId, role, permissions) {
|
||||
if (!document.getElementById(formId).reportValidity()) {
|
||||
if (!this.validate(formId, role, permissions)) {
|
||||
return;
|
||||
}
|
||||
Object.entries(permissions).forEach(entry => {
|
||||
|
@ -105,6 +105,21 @@
|
|||
return 'delete';
|
||||
}
|
||||
return key;
|
||||
},
|
||||
validate(formId, role, permissions) {
|
||||
const form = document.getElementById(formId);
|
||||
const input = document.getElementById(`${formId}Input`);
|
||||
|
||||
input.setCustomValidity('');
|
||||
|
||||
if (!Object.values(permissions).some(p => p)) {
|
||||
input.setCustomValidity('No permissions selected');
|
||||
}
|
||||
if (this.permissions.some(p => p.role === role)) {
|
||||
input.setCustomValidity('Role entry already exists');
|
||||
}
|
||||
|
||||
return form.reportValidity();
|
||||
}
|
||||
}));
|
||||
Alpine.data('permissionsRow', () => ({
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: 'UTC',
|
||||
return new Intl.DateTimeFormat(navigator.languages, {
|
||||
hourCycle: 'h24',
|
||||
...format
|
||||
}).format(new Date(datetime));
|
||||
|
|
|
@ -89,6 +89,10 @@
|
|||
max-width: 800px;
|
||||
}
|
||||
|
||||
&.width-xlarge {
|
||||
max-width: 950px;
|
||||
}
|
||||
|
||||
&.open {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
.permissions-matrix {
|
||||
th:first-child, td:first-child {
|
||||
width: 100px;
|
||||
width: 35%;
|
||||
}
|
||||
th:not(:first-child):not(:last-child), td:not(:first-child):not(:last-child) {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
th:last-child, td:last-child {
|
||||
width: 20px;
|
||||
width: 10%;
|
||||
}
|
||||
td {
|
||||
vertical-align: middle;
|
||||
|
|
200
src/Appwrite/Auth/OAuth2/Etsy.php
Normal file
200
src/Appwrite/Auth/OAuth2/Etsy.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
class Etsy extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://api.etsy.com/v3/public';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $version = '2022-07-14';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
"email_r",
|
||||
"profile_r",
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $pkce = '';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getPKCE(): string
|
||||
{
|
||||
if (empty($this->pkce)) {
|
||||
$this->pkce = \bin2hex(\random_bytes(rand(43, 128)));
|
||||
}
|
||||
|
||||
return $this->pkce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'etsy';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return 'https://www.etsy.com/oauth/connect/oauth/authorize?' . \http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'response_type' => 'code',
|
||||
'state' => \json_encode($this->state),
|
||||
'scope' => $this->scopes,
|
||||
'code_challenge' => $this->getPKCE(),
|
||||
'code_challenge_method' => 'S256',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'code' => $code,
|
||||
'code_verifier' => $this->getPKCE(),
|
||||
])
|
||||
), true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'client_id' => $this->appID,
|
||||
'refresh_token' => $refreshToken,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$components = explode('.', $accessToken);
|
||||
|
||||
return $components[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
return $this->getUser($accessToken)['primary_email'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* OAuth is only allowed if account has been verified through Etsy, itself.
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$email = $this->getUserEmail($accessToken);
|
||||
|
||||
return !empty($email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
return $this->getUser($accessToken)['login_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (!empty($this->user)) {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
$headers = ['Authorization: Bearer ' . $accessToken];
|
||||
|
||||
$this->user = \json_decode($this->request(
|
||||
'GET',
|
||||
'https://api.etsy.com/v3/application/users/' . $this->getUserID($accessToken),
|
||||
), true);
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
|
@ -304,7 +304,7 @@ class OpenAPI3 extends Format
|
|||
case 'Utopia\Database\Validator\DatetimeValidator':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'datetime';
|
||||
$node['schema']['x-example'] = '2022-06-15T13:45:30.496';
|
||||
$node['schema']['x-example'] = Model::TYPE_DATETIME_EXAMPLE;
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
|
|
|
@ -300,7 +300,7 @@ class Swagger2 extends Format
|
|||
case 'Utopia\Database\Validator\DatetimeValidator':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'datetime';
|
||||
$node['x-example'] = '2022-06-15T13:45:30.496';
|
||||
$node['x-example'] = Model::TYPE_DATETIME_EXAMPLE;
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
$node['type'] = $validator->getType();
|
||||
|
|
|
@ -114,6 +114,7 @@ class Queries extends Validator
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Buckets extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Collections extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Databases extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Deployments extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Executions extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Files extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Functions extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Memberships extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Teams extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Base;
|
||||
|
||||
class Users extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
|
|
|
@ -54,15 +54,6 @@ abstract class Base extends Validator
|
|||
return self::TYPE_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isValid($query): bool;
|
||||
|
||||
/**
|
||||
* Returns what type of query this Validator is for
|
||||
*/
|
||||
|
|
|
@ -58,6 +58,19 @@ class Key extends Model
|
|||
'default' => '',
|
||||
'example' => '919c2d18fb5d4...a2ae413da83346ad2',
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in Unix timestamp.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE
|
||||
])
|
||||
->addRule('sdks', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'List of SDK user agents that used this key.',
|
||||
'default' => null,
|
||||
'example' => 'appwrite:flutter',
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class User extends Model
|
|||
->addRule('registration', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'User registration date in Datetime.',
|
||||
'default' => null,
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('status', [
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
namespace Tests\E2E\Scopes;
|
||||
|
||||
use Appwrite\Tests\Retryable;
|
||||
use Tests\E2E\Client;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
abstract class Scope extends TestCase
|
||||
{
|
||||
use Retryable;
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Appwrite\Tests\Retry;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\DateTime;
|
||||
|
@ -518,6 +519,7 @@ trait AccountBase
|
|||
/**
|
||||
* @depends testUpdateAccountName
|
||||
*/
|
||||
#[Retry(count: 1)]
|
||||
public function testUpdateAccountPassword($data): array
|
||||
{
|
||||
$email = $data['email'] ?? '';
|
||||
|
|
|
@ -31,7 +31,7 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
$this->assertEquals('InvalidDocumentDatabase', $database['body']['name']);
|
||||
|
||||
$databaseId = $database['body']['$id'];
|
||||
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
$publicMovies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'permissions' => [
|
||||
|
@ -40,12 +40,23 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]);
|
||||
$privateMovies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'permissions' => [],
|
||||
'documentSecurity' => true,
|
||||
]);
|
||||
|
||||
$collection = ['id' => $movies['body']['$id']];
|
||||
$publicCollection = ['id' => $publicMovies['body']['$id']];
|
||||
$privateCollection = ['id' => $privateMovies['body']['$id']];
|
||||
|
||||
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection['id'] . '/attributes/string', $this->getServerHeader(), [
|
||||
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollection['id'] . '/attributes/string', $this->getServerHeader(), [
|
||||
'key' => 'title',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
]);
|
||||
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollection['id'] . '/attributes/string', $this->getServerHeader(), [
|
||||
'key' => 'title',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
|
@ -53,13 +64,14 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
|
||||
sleep(2);
|
||||
|
||||
return ['collectionId' => $collection['id'], 'databaseId' => $databaseId];
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'publicCollectionId' => $publicCollection['id'],
|
||||
'privateCollectionId' => $privateCollection['id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* [string[] $permissions]
|
||||
*/
|
||||
public function readDocumentsProvider()
|
||||
public function permissionsProvider(): array
|
||||
{
|
||||
return [
|
||||
[[Permission::read(Role::any())]],
|
||||
|
@ -72,14 +84,23 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
}
|
||||
|
||||
/**
|
||||
* @dataProvider readDocumentsProvider
|
||||
* @dataProvider permissionsProvider
|
||||
*/
|
||||
public function testReadDocuments($permissions)
|
||||
{
|
||||
$data = $this->createCollection();
|
||||
$collectionId = $data['collectionId'];
|
||||
$publicCollectionId = $data['publicCollectionId'];
|
||||
$privateCollectionId = $data['privateCollectionId'];
|
||||
$databaseId = $data['databaseId'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', $this->getServerHeader(), [
|
||||
|
||||
$publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
$privateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
|
@ -87,18 +108,118 @@ class DatabasesPermissionsGuestTest extends Scope
|
|||
'permissions' => $permissions,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals(201, $publicResponse['headers']['status-code']);
|
||||
$this->assertEquals(201, $privateResponse['headers']['status-code']);
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
Authorization::cleanRoles();
|
||||
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
|
||||
$publicDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
$privateDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $documents['body']['total']);
|
||||
$this->assertEquals($permissions, $documents['body']['documents'][0]['$permissions']);
|
||||
$this->assertEquals(1, $publicDocuments['body']['total']);
|
||||
$this->assertEquals($permissions, $publicDocuments['body']['documents'][0]['$permissions']);
|
||||
|
||||
if (\in_array(Permission::read(Role::any()), $permissions)) {
|
||||
$this->assertEquals(1, $privateDocuments['body']['total']);
|
||||
$this->assertEquals($permissions, $privateDocuments['body']['documents'][0]['$permissions']);
|
||||
} else {
|
||||
$this->assertEquals(0, $privateDocuments['body']['total']);
|
||||
}
|
||||
|
||||
foreach ($roles as $role) {
|
||||
Authorization::setRole($role);
|
||||
}
|
||||
}
|
||||
|
||||
public function testWriteDocument()
|
||||
{
|
||||
$data = $this->createCollection();
|
||||
$publicCollectionId = $data['publicCollectionId'];
|
||||
$privateCollectionId = $data['privateCollectionId'];
|
||||
$databaseId = $data['databaseId'];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
Authorization::cleanRoles();
|
||||
|
||||
$publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
]
|
||||
]);
|
||||
|
||||
$publicDocumentId = $publicResponse['body']['$id'];
|
||||
$this->assertEquals(201, $publicResponse['headers']['status-code']);
|
||||
|
||||
$privateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $privateResponse['headers']['status-code']);
|
||||
|
||||
// Create a document in private collection with API key so we can test that update and delete are also not allowed
|
||||
$privateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Lorem',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $privateResponse['headers']['status-code']);
|
||||
$privateDocumentId = $privateResponse['body']['$id'];
|
||||
|
||||
$publicDocument = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'data' => [
|
||||
'title' => 'Thor: Ragnarok',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $publicDocument['headers']['status-code']);
|
||||
$this->assertEquals('Thor: Ragnarok', $publicDocument['body']['title']);
|
||||
|
||||
$privateDocument = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'data' => [
|
||||
'title' => 'Thor: Ragnarok',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $privateDocument['headers']['status-code']);
|
||||
|
||||
$publicDocument = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $publicDocument['headers']['status-code']);
|
||||
|
||||
$privateDocument = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $privateDocument['headers']['status-code']);
|
||||
|
||||
foreach ($roles as $role) {
|
||||
Authorization::setRole($role);
|
||||
|
|
|
@ -280,7 +280,75 @@ class FunctionsCustomClientTest extends Scope
|
|||
];
|
||||
}
|
||||
|
||||
public function testCreateExecutionUnauthorized(): array
|
||||
public function testCreateCustomExecutionGuest()
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$apikey = $this->getProject()['apiKey'];
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [Role::any()->toString()],
|
||||
'runtime' => 'php-8.0',
|
||||
'vars' => [
|
||||
'funcKey1' => 'funcValue1',
|
||||
'funcKey2' => 'funcValue2',
|
||||
'funcKey3' => 'funcValue3',
|
||||
],
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
$folder = 'php-fn';
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||
$this->packageCode($folder);
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
||||
// Wait for deployment to be built.
|
||||
sleep(10);
|
||||
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
// Why do we have to do this?
|
||||
$function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], []);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'data' => 'foobar',
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $execution['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateExecutionNoDeployment(): array
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', [
|
||||
'content-type' => 'application/json',
|
||||
|
@ -301,7 +369,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
'async' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $execution['headers']['status-code']);
|
||||
$this->assertEquals(404, $execution['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\E2E\Services\Functions;
|
||||
|
||||
use Appwrite\Tests\Retry;
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
|
@ -1371,6 +1372,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
#[Retry(count: 1)]
|
||||
public function testCreateCustomRubyExecution()
|
||||
{
|
||||
$name = 'ruby-3.1';
|
||||
|
|
|
@ -851,6 +851,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-key' => $keySecret,
|
||||
'x-sdk-name' => 'python'
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
@ -859,6 +860,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-key' => $keySecret,
|
||||
'x-sdk-name' => 'php'
|
||||
]), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Arsenal'
|
||||
|
@ -866,6 +868,21 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
/** Check that the API key has been updated */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertCount(2, $response['body']['sdks']);
|
||||
$this->assertContains('python', $response['body']['sdks']);
|
||||
$this->assertContains('php', $response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['accessedAt']);
|
||||
|
||||
// Cleanup
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/keys/' . $keyId, array_merge([
|
||||
|
@ -1183,6 +1200,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('teams.read', $response['body']['scopes']);
|
||||
$this->assertContains('teams.write', $response['body']['scopes']);
|
||||
$this->assertNotEmpty($response['body']['secret']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
$data = array_merge($data, [
|
||||
'keyId' => $response['body']['$id'],
|
||||
|
@ -1252,6 +1273,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('teams.write', $response['body']['scopes']);
|
||||
$this->assertCount(2, $response['body']['scopes']);
|
||||
$this->assertNotEmpty($response['body']['secret']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
@ -1360,6 +1385,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('users.write', $response['body']['scopes']);
|
||||
$this->assertContains('collections.read', $response['body']['scopes']);
|
||||
$this->assertCount(3, $response['body']['scopes']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
@ -1374,6 +1403,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('users.write', $response['body']['scopes']);
|
||||
$this->assertContains('collections.read', $response['body']['scopes']);
|
||||
$this->assertCount(3, $response['body']['scopes']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
|
|
@ -49,13 +49,10 @@ class StorageCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$this->assertNotEmpty($bucketId);
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
Authorization::cleanRoles();
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
], [
|
||||
'fileId' => ID::unique(),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
|
||||
]);
|
||||
|
@ -68,38 +65,47 @@ class StorageCustomClientTest extends Scope
|
|||
$this->assertEquals('image/png', $file['body']['mimeType']);
|
||||
$this->assertEquals(47218, $file['body']['sizeOriginal']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
$file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
], [
|
||||
'name' => 'permissions.png',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $file['headers']['status-code']);
|
||||
$this->assertEmpty($file['body']);
|
||||
|
@ -173,6 +179,15 @@ class StorageCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'permissions.png',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
@ -289,6 +304,15 @@ class StorageCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'permissions.png',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
@ -297,7 +321,7 @@ class StorageCustomClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($file['headers']['status-code'], 401);
|
||||
$this->assertEquals(401, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
|
@ -307,6 +331,15 @@ class StorageCustomClientTest extends Scope
|
|||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
|
||||
]);
|
||||
|
||||
$this->client->call(CLient::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'name' => 'permissions.png',
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $file['headers']['status-code']);
|
||||
|
||||
$this->assertEquals($file['headers']['status-code'], 401);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
|
@ -583,35 +616,31 @@ class StorageCustomClientTest extends Scope
|
|||
$this->assertEquals('image/png', $file1['body']['mimeType']);
|
||||
$this->assertEquals(47218, $file1['body']['sizeOriginal']);
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', array_merge([
|
||||
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
|
||||
|
@ -628,16 +657,12 @@ class StorageCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(401, $file['headers']['status-code']);
|
||||
|
||||
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $file['headers']['status-code']);
|
||||
|
||||
foreach ($roles as $role) {
|
||||
Authorization::setRole($role);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFileUsersPermissions(): void
|
||||
|
|
|
@ -19,7 +19,8 @@ trait TeamsBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Arsenal'
|
||||
'name' => 'Arsenal',
|
||||
'roles' => ['player'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response1['headers']['status-code']);
|
||||
|
|
|
@ -30,7 +30,8 @@ trait TeamsBaseClient
|
|||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
|
||||
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
|
||||
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
|
||||
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
|
||||
|
||||
$membershipId = $response['body']['memberships'][0]['$id'];
|
||||
|
||||
|
@ -87,7 +88,8 @@ trait TeamsBaseClient
|
|||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
|
||||
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
|
||||
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
|
||||
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
@ -102,7 +104,8 @@ trait TeamsBaseClient
|
|||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
|
||||
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
|
||||
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
|
||||
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\E2E\Services\Webhooks;
|
||||
|
||||
use Appwrite\Tests\Retry;
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\DateTime;
|
||||
|
@ -304,6 +305,7 @@ trait WebhooksBase
|
|||
/**
|
||||
* @depends testCreateCollection
|
||||
*/
|
||||
#[Retry(count: 1)]
|
||||
public function testDeleteDocument(array $data): array
|
||||
{
|
||||
$actorsId = $data['actorsId'];
|
||||
|
|
16
tests/extensions/Retry.php
Normal file
16
tests/extensions/Retry.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
/**
|
||||
* Allows test methods to be retried if they fail.
|
||||
*
|
||||
* Requires that the test class extends {@see TestCase} and has trait {@see Retryable}.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
class Retry
|
||||
{
|
||||
public function __construct(protected int $count = 1)
|
||||
{
|
||||
}
|
||||
}
|
70
tests/extensions/Retryable.php
Normal file
70
tests/extensions/Retryable.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Allows test methods annotated with {@see Retry} to be retried.
|
||||
*/
|
||||
trait Retryable
|
||||
{
|
||||
/**
|
||||
* Custom runBare, hides and defers to PHPUnit {@see TestCase} runBare function,
|
||||
* accounting for any retries configured by the {@see Retry} annotation.
|
||||
*
|
||||
* @return void
|
||||
* @throws \ReflectionException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function runBare(): void
|
||||
{
|
||||
$retries = $this->getNumberOfRetries();
|
||||
$ex = null;
|
||||
for ($i = 0; $i <= $retries; ++$i) {
|
||||
try {
|
||||
parent::runBare();
|
||||
return;
|
||||
} catch (\Throwable | \Exception $ex) {
|
||||
// Swallow the exception until we have exhausted our retries.
|
||||
if ($i !== $retries) {
|
||||
echo 'Flaky test failed, retrying...' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($ex) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
private function getNumberOfRetries(): int
|
||||
{
|
||||
$root = new \ReflectionClass($this);
|
||||
$case = $this->getTestCaseRoot($root);
|
||||
$name = $case->getProperty('name');
|
||||
$name->setAccessible(true);
|
||||
$name = $name->getValue($this);
|
||||
$method = $root->getMethod($name);
|
||||
$attributes = $method->getAttributes(Retry::class);
|
||||
$attribute = $attributes[0] ?? null;
|
||||
$args = $attribute?->getArguments();
|
||||
$retries = $args['count'] ?? 0;
|
||||
return \max(0, $retries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ReflectionClass $reflection
|
||||
* @return \ReflectionClass
|
||||
*/
|
||||
private function getTestCaseRoot(\ReflectionClass $reflection): \ReflectionClass
|
||||
{
|
||||
if ($reflection->getName() === TestCase::class) {
|
||||
return $reflection;
|
||||
}
|
||||
return $this->getTestCaseRoot($reflection->getParentClass());
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Stats;
|
||||
namespace Tests\Unit\Usage;
|
||||
|
||||
use Appwrite\Usage\Stats;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
|
@ -16,7 +16,7 @@ class LimitTest extends TestCase
|
|||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Limit();
|
||||
$this->validator = new Limit(100);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
|
|
|
@ -16,7 +16,7 @@ class OffsetTest extends TestCase
|
|||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Offset();
|
||||
$this->validator = new Offset(5000);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
|
|
|
@ -50,5 +50,6 @@ class OrderTest extends TestCase
|
|||
$this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderAsc('dne')), false, $this->validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue