restructure endpoints

This commit is contained in:
Damodar Lohani
2024-12-04 08:25:55 +00:00
parent d32dc0a4d9
commit 11bf99c871
5 changed files with 152 additions and 138 deletions
@@ -0,0 +1,45 @@
<?php
namespace Appwrite\Platform\Modules\Tokens\Http\Tokens\Buckets\Files;
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action as UtopiaAction;
class Action extends UtopiaAction
{
protected function getFileAndBucket(Database $dbForProject, string $bucketId, string $fileId): array
{
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
return [
'bucket' => $bucket,
'file' => $file,
];
}
}
@@ -0,0 +1,86 @@
<?php
namespace Appwrite\Platform\Modules\Tokens\Http\Tokens\Buckets\Files;
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Nullable;
class CreateFileToken extends Action
{
use HTTP;
public static function getName()
{
return 'createFileToken';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tokens/buckets/:bucketId/files/:fileId')
->desc('Create file token')
->groups(['api', 'token'])
->label('scope', 'tokens.write')
->label('audits.event', 'token.create')
->label('event', 'tokens.[tokenId].create')
->label('audits.resource', 'token/{response.$id}')
->label('usage.metric', 'tokens.{scope}.requests.create')
->label('usage.params', ['resourceId:{request.resourceId}', 'resourceType:{request.resourceType}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'tokens')
->label('sdk.method', 'createFileToken')
->label('sdk.description', '/docs/references/tokens/create_file_token.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_RESOURCE_TOKEN)
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Token expiry date', true)
->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->callback(fn ($bucketId, $fileId, $expire, $permissions, $response, $dbForProject, $user, $queueForEvents) => $this->action($bucketId, $fileId, $expire, $permissions, $response, $dbForProject, $user, $queueForEvents));
}
public function action(string $bucketId, string $fileId, ?string $expire, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents)
{
['bucket' => $bucket, 'file' => $file] = $this->getFileAndBucket($dbForProject, $bucketId, $fileId);
$token = $dbForProject->createDocument('resourceTokens', new Document([
'$id' => ID::unique(),
'secret' => Auth::tokenGenerator(128),
'resourceId' => $bucketId . ':' . $fileId,
'resourceInternalId' => $bucket->getInternalId() . ':' . $file->getInternalId(),
'resourceType' => 'file',
'expire' => $expire,
'$permissions' => $permissions
]));
$queueForEvents
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setParam('tokenId', $token->getId())
->setContext('bucket', $bucket)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($token, Response::MODEL_RESOURCE_TOKEN);
}
}
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Tokens\Http\Tokens;
namespace Appwrite\Platform\Modules\Tokens\Http\Tokens\Buckets\Files;
use Appwrite\Extend\Exception as ExtendException;
use Appwrite\Utopia\Database\Validator\Queries\FileTokens;
@@ -9,23 +9,23 @@ use Exception;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
class ListTokens extends Action
class ListFileTokens extends Action
{
use HTTP;
public static function getName()
{
return 'listTokens';
return 'listFileTokens';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/storage/buckets/:bucketId/files/:fileId/tokens')
->setHttpPath('/v1/tokens/buckets/:bucketId/files/:fileId')
->desc('List tokens')
->groups(['api', 'tokens'])
->label('scope', 'tokens.read')
@@ -37,15 +37,21 @@ class ListTokens extends Action
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_RESOURCE_TOKEN_LIST)
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('queries', [], new FileTokens(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', FileTokens::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->callback(fn ($queries, $response, $dbForProject) => $this->action($queries, $response, $dbForProject));
->callback(fn ($bucketId, $fileId, $queries, $response, $dbForProject) => $this->action($bucketId, $fileId, $queries, $response, $dbForProject));
}
public function action(array $queries, Response $response, Database $dbForProject)
public function action(string $bucketId, string $fileId, array $queries, Response $response, Database $dbForProject)
{
['bucket' => $bucket, 'file' => $file] = $this->getFileAndBucket($dbForProject, $bucketId, $fileId);
$queries = Query::parseQueries($queries);
$queries[] = Query::equal('resourceType', ["files"]);
$queries[] = Query::equal('resourceId', [$bucket->getInternalId() . ':' . $file->getInternalId()]);
// Get cursor document if there was a cursor query
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
@@ -1,123 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Tokens\Http\Tokens;
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
class CreateToken extends Action
{
use HTTP;
public static function getName()
{
return 'createToken';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tokens')
->desc('Create token')
->groups(['api', 'token'])
->label('scope', 'tokens.write')
->label('audits.event', 'token.create')
->label('event', 'tokens.[tokenId].create')
->label('audits.resource', 'token/{response.$id}')
->label('usage.metric', 'tokens.{scope}.requests.create')
->label('usage.params', ['resourceId:{request.resourceId}', 'resourceType:{request.resourceType}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'tokens')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/tokens/create.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_RESOURCE_TOKEN)
->param('resourceType', '', new WhiteList(['files']), 'Resource type one of [files].')
->param('resourceId', '', new UID(), 'Unique resource ID.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Token expiry date', true)
->param('permissions', [], new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->callback(fn ($resourceType, $resourceId, $expire, $permissions, $response, $dbForProject, $user, $queueForEvents) => $this->action($resourceType, $resourceId, $expire, $permissions, $response, $dbForProject, $user, $queueForEvents));
}
public function action(string $resourceType, string $resourceId, ?string $expire, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents)
{
if ($resourceType === 'files') {
$ids = explode(':', $resourceId);
if (count($ids) !== 2) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource id');
}
$bucketId = $ids[0];
$fileId = $ids[1];
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$token = $dbForProject->createDocument('resourceTokens', new Document([
'$id' => ID::unique(),
'secret' => Auth::tokenGenerator(128),
'resourceId' => $bucketId . ':' . $fileId,
'resourceInternalId' => $bucket->getInternalId() . ':' . $file->getInternalId(),
'resourceType' => 'file',
'expire' => $expire,
'$permissions' => $permissions
]));
$queueForEvents
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setParam('tokenId', $token->getId())
->setContext('bucket', $bucket)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($token, Response::MODEL_RESOURCE_TOKEN);
} else {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource type');
}
}
}
@@ -24,15 +24,15 @@ class GetTokenJWT extends Action
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/storage/buckets/:bucketId/files/:fileId/tokens/:tokenId/jwt')
->desc('Get file token jwt')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->setHttpPath('/v1/tokens/:tokenId/jwt')
->desc('Get token as JWT')
->groups(['api', 'tokens'])
->label('scope', 'tokens.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('usage.metric', 'fileTokens.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}','fileId:{request.fileId}'])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileTokenJWT')
->label('usage.metric', 'tokens.{scope}.requests.read')
->label('usage.params', ['tokenId:{request.tokenId}'])
->label('sdk.namespace', 'tokens')
->label('sdk.method', 'getJWT')
->label('sdk.description', '/docs/references/storage/get-file-token-jwt.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)