diff --git a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/Action.php b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/Action.php new file mode 100644 index 0000000000..612710e4d5 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/Action.php @@ -0,0 +1,45 @@ + $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, + ]; + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/CreateFileToken.php b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/CreateFileToken.php new file mode 100644 index 0000000000..7f5ac38fea --- /dev/null +++ b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/CreateFileToken.php @@ -0,0 +1,86 @@ +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); + } +} diff --git a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/ListTokens.php b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/ListFileTokens.php similarity index 71% rename from src/Appwrite/Platform/Modules/Tokens/Http/Tokens/ListTokens.php rename to src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/ListFileTokens.php index 5b8948d357..88a6137654 100644 --- a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/ListTokens.php +++ b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/Buckets/Files/ListFileTokens.php @@ -1,6 +1,6 @@ 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]); diff --git a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/CreateToken.php b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/CreateToken.php deleted file mode 100644 index 490e4e08ed..0000000000 --- a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/CreateToken.php +++ /dev/null @@ -1,123 +0,0 @@ -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'); - } - } -} diff --git a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/GetTokenJWT.php b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/GetTokenJWT.php index 253b0b6fd0..225c9b78fb 100644 --- a/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/GetTokenJWT.php +++ b/src/Appwrite/Platform/Modules/Tokens/Http/Tokens/GetTokenJWT.php @@ -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)