From d38197380e4ddbdfcfbdab221bec25c9268966f5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 15 Feb 2024 22:36:34 +1300 Subject: [PATCH 1/5] Support adding image to push notification via storage file --- app/config/errors.php | 5 ++ app/controllers/api/messaging.php | 62 +++++++++++++++++++++-- src/Appwrite/Extend/Exception.php | 83 ++++++++++++++++--------------- 3 files changed, 106 insertions(+), 44 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 7932dcd3a9..8705b1f9d6 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -420,6 +420,11 @@ return [ 'description' => 'The value for x-appwrite-id header is invalid. Please check the value of the x-appwrite-id header is a valid id and not unique().', 'code' => 400, ], + Exception::STORAGE_FILE_NOT_PUBLIC => [ + 'name' => Exception::STORAGE_FILE_NOT_PUBLIC, + 'description' => 'The requested file is not publicly readable.', + 'code' => 403, + ], /** VCS */ Exception::INSTALLATION_NOT_FOUND => [ diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 0e865a7184..1995c8cc20 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -27,11 +27,13 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Validator\Key; use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Roles; use Utopia\Database\Validator\UID; +use Utopia\Domains\Domain; use Utopia\Locale\Locale; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; @@ -2775,6 +2777,7 @@ App::post('/v1/messaging/messages/push') ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) ->param('action', '', new Text(256), 'Action for push notification.', true) + ->param('image', '', new Key(), 'Image for push notification. Must be the ID of a jpeg or png image in Appwrite Storage.', true) ->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true) ->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true) ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) @@ -2788,7 +2791,7 @@ App::post('/v1/messaging/messages/push') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2819,9 +2822,35 @@ App::post('/v1/messaging/messages/push') } } + if (!empty($image)) { + $image = $dbForProject->getDocument('files', $image); + + if ($image->isEmpty()) { + throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); + } + + if (!\in_array(Permission::read(Role::any()), $image->getRead())) { + throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); + } + + if (!\in_array($image->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { + throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED); + } + + $host = App::getEnv('_APP_DOMAIN', 'localhost'); + $domain = new Domain(\parse_url($host, PHP_URL_HOST)); + $protocol = App::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; + + if (!$domain->isKnown()) { + throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); + } + + $image = "{$protocol}://{$host}/v1/storage/files/{$image->getId()}/view"; + } + $pushData = []; - $keys = ['title', 'body', 'data', 'action', 'icon', 'sound', 'color', 'tag', 'badge']; + $keys = ['title', 'body', 'data', 'action', 'image', 'icon', 'sound', 'color', 'tag', 'badge']; foreach ($keys as $key) { if (!empty($$key)) { @@ -3388,6 +3417,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('body', null, new Text(64230), 'Body for push notification.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) ->param('action', null, new Text(256), 'Action for push notification.', true) + ->param('image', null, new Key(), 'Image for push notification. Must be the ID of a jpeg or png image in Appwrite Storage.', true) ->param('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true) ->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true) ->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true) @@ -3401,7 +3431,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3471,6 +3501,32 @@ App::patch('/v1/messaging/messages/push/:messageId') $pushData['badge'] = $badge; } + if (!\is_null($image)) { + $image = $dbForProject->getDocument('files', $image); + + if ($image->isEmpty()) { + throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); + } + + if (!\in_array(Permission::read(Role::any()), $image->getRead())) { + throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); + } + + if (!\in_array($image->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { + throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED); + } + + $host = App::getEnv('_APP_DOMAIN', 'localhost'); + $domain = new Domain(\parse_url($host, PHP_URL_HOST)); + $protocol = App::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; + + if (!$domain->isKnown()) { + throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); + } + + $pushData['image'] = "{$protocol}://{$host}/v1/storage/files/{$image->getId()}/view";; + } + $message->setAttribute('data', $pushData); if (!\is_null($status)) { diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 98dee95c94..4d989367f0 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -92,8 +92,8 @@ class Exception extends \Exception public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request'; public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized'; public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error'; - public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified'; - public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified'; + public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_already_verified'; + public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified'; public const USER_TARGET_NOT_FOUND = 'user_target_not_found'; public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists'; @@ -130,18 +130,19 @@ class Exception extends \Exception public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range'; public const STORAGE_INVALID_RANGE = 'storage_invalid_range'; public const STORAGE_INVALID_APPWRITE_ID = 'storage_invalid_appwrite_id'; + public const STORAGE_FILE_NOT_PUBLIC = 'storage_file_not_public'; /** VCS */ - public const INSTALLATION_NOT_FOUND = 'installation_not_found'; - public const PROVIDER_REPOSITORY_NOT_FOUND = 'provider_repository_not_found'; - public const REPOSITORY_NOT_FOUND = 'repository_not_found'; - public const PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict'; - public const GENERAL_PROVIDER_FAILURE = 'general_provider_failure'; + public const INSTALLATION_NOT_FOUND = 'installation_not_found'; + public const PROVIDER_REPOSITORY_NOT_FOUND = 'provider_repository_not_found'; + public const REPOSITORY_NOT_FOUND = 'repository_not_found'; + public const PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict'; + public const GENERAL_PROVIDER_FAILURE = 'general_provider_failure'; /** Functions */ public const FUNCTION_NOT_FOUND = 'function_not_found'; public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported'; - public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; + public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; /** Deployments */ public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found'; @@ -213,10 +214,10 @@ class Exception extends \Exception public const ROUTER_DOMAIN_NOT_CONFIGURED = 'router_domain_not_configured'; /** Proxy */ - public const RULE_RESOURCE_NOT_FOUND = 'rule_resource_not_found'; - public const RULE_NOT_FOUND = 'rule_not_found'; - public const RULE_ALREADY_EXISTS = 'rule_already_exists'; - public const RULE_VERIFICATION_FAILED = 'rule_verification_failed'; + public const RULE_RESOURCE_NOT_FOUND = 'rule_resource_not_found'; + public const RULE_NOT_FOUND = 'rule_not_found'; + public const RULE_ALREADY_EXISTS = 'rule_already_exists'; + public const RULE_VERIFICATION_FAILED = 'rule_verification_failed'; /** Keys */ public const KEY_NOT_FOUND = 'key_not_found'; @@ -233,49 +234,49 @@ class Exception extends \Exception public const GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries'; /** Migrations */ - public const MIGRATION_NOT_FOUND = 'migration_not_found'; - public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists'; - public const MIGRATION_IN_PROGRESS = 'migration_in_progress'; - public const MIGRATION_PROVIDER_ERROR = 'migration_provider_error'; + public const MIGRATION_NOT_FOUND = 'migration_not_found'; + public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists'; + public const MIGRATION_IN_PROGRESS = 'migration_in_progress'; + public const MIGRATION_PROVIDER_ERROR = 'migration_provider_error'; /** Realtime */ - public const REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid'; - public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages'; - public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation'; + public const REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid'; + public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages'; + public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation'; /** Health */ - public const HEALTH_QUEUE_SIZE_EXCEEDED = 'health_queue_size_exceeded'; - public const HEALTH_CERTIFICATE_EXPIRED = 'health_certificate_expired'; - public const HEALTH_INVALID_HOST = 'health_invalid_host'; + public const HEALTH_QUEUE_SIZE_EXCEEDED = 'health_queue_size_exceeded'; + public const HEALTH_CERTIFICATE_EXPIRED = 'health_certificate_expired'; + public const HEALTH_INVALID_HOST = 'health_invalid_host'; /** Provider */ - public const PROVIDER_NOT_FOUND = 'provider_not_found'; - public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists'; - public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; - public const PROVIDER_MISSING_CREDENTIALS = 'provider_missing_credentials'; + public const PROVIDER_NOT_FOUND = 'provider_not_found'; + public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists'; + public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; + public const PROVIDER_MISSING_CREDENTIALS = 'provider_missing_credentials'; /** Topic */ - public const TOPIC_NOT_FOUND = 'topic_not_found'; - public const TOPIC_ALREADY_EXISTS = 'topic_already_exists'; + public const TOPIC_NOT_FOUND = 'topic_not_found'; + public const TOPIC_ALREADY_EXISTS = 'topic_already_exists'; /** Subscriber */ - public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found'; - public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists'; + public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found'; + public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists'; /** Message */ - public const MESSAGE_NOT_FOUND = 'message_not_found'; - public const MESSAGE_MISSING_TARGET = 'message_missing_target'; - public const MESSAGE_ALREADY_SENT = 'message_already_sent'; - public const MESSAGE_ALREADY_PROCESSING = 'message_already_processing'; - public const MESSAGE_ALREADY_FAILED = 'message_already_failed'; - public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled'; - public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email'; - public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms'; - public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push'; - public const MESSAGE_MISSING_SCHEDULE = 'message_missing_schedule'; + public const MESSAGE_NOT_FOUND = 'message_not_found'; + public const MESSAGE_MISSING_TARGET = 'message_missing_target'; + public const MESSAGE_ALREADY_SENT = 'message_already_sent'; + public const MESSAGE_ALREADY_PROCESSING = 'message_already_processing'; + public const MESSAGE_ALREADY_FAILED = 'message_already_failed'; + public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled'; + public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email'; + public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms'; + public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push'; + public const MESSAGE_MISSING_SCHEDULE = 'message_missing_schedule'; /** Schedules */ - public const SCHEDULE_NOT_FOUND = 'schedule_not_found'; + public const SCHEDULE_NOT_FOUND = 'schedule_not_found'; protected string $type = ''; From 2fffbd93f86dda6b024c21eb78b6833fff067b0b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 15 Feb 2024 22:41:45 +1300 Subject: [PATCH 2/5] Lint fix --- app/controllers/api/messaging.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 1995c8cc20..6c664ec362 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -3524,7 +3524,7 @@ App::patch('/v1/messaging/messages/push/:messageId') throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } - $pushData['image'] = "{$protocol}://{$host}/v1/storage/files/{$image->getId()}/view";; + $pushData['image'] = "{$protocol}://{$host}/v1/storage/files/{$image->getId()}/view"; } $message->setAttribute('data', $pushData); From 7a420000faf1e54587e0ec8b947bc1c118d972c5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 19 Feb 2024 20:48:52 +1300 Subject: [PATCH 3/5] Fix image permissions check and URL --- app/controllers/api/messaging.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index d77af1874d..721b286333 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -2856,7 +2856,9 @@ App::post('/v1/messaging/messages/push') throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); } - if (!\in_array(Permission::read(Role::any()), $image->getRead())) { + $bucket = $dbForProject->getDocument('buckets', $image->getAttribute('bucketId')); + + if (!\in_array(Permission::read(Role::any()), \array_merge($image->getRead(), $bucket->getRead()))) { throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } @@ -2872,7 +2874,7 @@ App::post('/v1/messaging/messages/push') throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } - $image = "{$protocol}://{$host}/v1/storage/files/{$image->getId()}/view"; + $image = "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$image->getId()}/view?project={$project->getId()}"; } $pushData = []; From 4bc6c202d9a464f3d58468cf3304d4e0c44252e9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 20 Feb 2024 18:25:57 +1300 Subject: [PATCH 4/5] Use compound ID for push notification image --- app/controllers/api/messaging.php | 20 ++++++++++++-------- src/Appwrite/Platform/Workers/Messaging.php | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 232582f199..c3112e1179 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -2830,7 +2830,7 @@ App::post('/v1/messaging/messages/push') ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) ->param('action', '', new Text(256), 'Action for push notification.', true) - ->param('image', '', new Key(), 'Image for push notification. Must be the ID of a jpeg or png image in Appwrite Storage.', true) + ->param('image', '', new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage.', true) ->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true) ->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true) ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) @@ -2876,19 +2876,23 @@ App::post('/v1/messaging/messages/push') } if (!empty($image)) { - $image = $dbForProject->getDocument('files', $image); + [$bucketId, $fileId] = CompoundUID::parse($image); - if ($image->isEmpty()) { - throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); + $bucket = $dbForProject->getDocument('buckets', $bucketId); + if ($bucket->isEmpty()) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - $bucket = $dbForProject->getDocument('buckets', $image->getAttribute('bucketId')); + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); + if ($file->isEmpty()) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } - if (!\in_array(Permission::read(Role::any()), \array_merge($image->getRead(), $bucket->getRead()))) { + if (!\in_array(Permission::read(Role::any()), \array_merge($file->getRead(), $bucket->getRead()))) { throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } - if (!\in_array($image->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { + if (!\in_array($file->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED); } @@ -2900,7 +2904,7 @@ App::post('/v1/messaging/messages/push') throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } - $image = "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$image->getId()}/view?project={$project->getId()}"; + $image = "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/view?project={$project->getId()}"; } $pushData = []; diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index ef2d553072..a54f6e49f9 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -636,6 +636,7 @@ class Messaging extends Action $body = $message['data']['body']; $data = $message['data']['data'] ?? null; $action = $message['data']['action'] ?? null; + $image = $message['data']['image'] ?? null; $sound = $message['data']['sound'] ?? null; $icon = $message['data']['icon'] ?? null; $color = $message['data']['color'] ?? null; @@ -649,6 +650,7 @@ class Messaging extends Action $data, $action, $sound, + $image, $icon, $color, $tag, From 3aeb62377c42948e02dbddbb645f676bc3a28388 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 21 Feb 2024 17:07:52 +1300 Subject: [PATCH 5/5] Fix missed update with image --- app/controllers/api/messaging.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index a19e734ac0..abd52f8ced 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -3471,7 +3471,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('body', null, new Text(64230), 'Body for push notification.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) ->param('action', null, new Text(256), 'Action for push notification.', true) - ->param('image', null, new Key(), 'Image for push notification. Must be the ID of a jpeg or png image in Appwrite Storage.', true) + ->param('image', null, new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage.', true) ->param('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true) ->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true) ->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true) @@ -3556,17 +3556,23 @@ App::patch('/v1/messaging/messages/push/:messageId') } if (!\is_null($image)) { - $image = $dbForProject->getDocument('files', $image); + [$bucketId, $fileId] = CompoundUID::parse($image); - if ($image->isEmpty()) { - throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); + $bucket = $dbForProject->getDocument('buckets', $bucketId); + if ($bucket->isEmpty()) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - if (!\in_array(Permission::read(Role::any()), $image->getRead())) { + $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); + if ($file->isEmpty()) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } + + if (!\in_array(Permission::read(Role::any()), \array_merge($file->getRead(), $bucket->getRead()))) { throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } - if (!\in_array($image->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { + if (!\in_array($file->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) { throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED); } @@ -3578,7 +3584,7 @@ App::patch('/v1/messaging/messages/push/:messageId') throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC); } - $pushData['image'] = "{$protocol}://{$host}/v1/storage/files/{$image->getId()}/view"; + $pushData['image'] = "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/view?project={$project->getId()}"; } $message->setAttribute('data', $pushData);