From 571d04311fe6d67fa0a3403a0efa04b791386b06 Mon Sep 17 00:00:00 2001 From: Steven Brookes <43211757+stevenbrookes@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:32:45 +0100 Subject: [PATCH] Add SQS AddPermission operation (#1773) * Add SQS AddPermission operation * Add missing SQS AddPermission test * Fix PHPCSFixer issue * Add to changelog * Fixup changelog * Fix order * Fixup composer --- CHANGELOG.md | 4 + composer.json | 2 +- src/Input/AddPermissionRequest.php | 218 ++++++++++++++++++ src/SqsClient.php | 58 +++++ tests/Integration/SqsClientTest.php | 16 ++ tests/Unit/Input/AddPermissionRequestTest.php | 35 +++ tests/Unit/SqsClientTest.php | 17 ++ 7 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 src/Input/AddPermissionRequest.php create mode 100644 tests/Unit/Input/AddPermissionRequestTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ce5ca3..a966096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## NOT RELEASED +### Added + +- Add AddPermission endpoint + ### Changed - Enable compiler optimization for the `sprintf` function. diff --git a/composer.json b/composer.json index 871fafc..6ae2ad1 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } } } diff --git a/src/Input/AddPermissionRequest.php b/src/Input/AddPermissionRequest.php new file mode 100644 index 0000000..66dea9b --- /dev/null +++ b/src/Input/AddPermissionRequest.php @@ -0,0 +1,218 @@ +queueUrl = $input['QueueUrl'] ?? null; + $this->label = $input['Label'] ?? null; + $this->awsAccountIds = $input['AWSAccountIds'] ?? null; + $this->actions = $input['Actions'] ?? null; + parent::__construct($input); + } + + /** + * @param array{ + * QueueUrl?: string, + * Label?: string, + * AWSAccountIds?: string[], + * Actions?: string[], + * '@region'?: string|null, + * }|AddPermissionRequest $input + */ + public static function create($input): self + { + return $input instanceof self ? $input : new self($input); + } + + /** + * @return string[] + */ + public function getActions(): array + { + return $this->actions ?? []; + } + + /** + * @return string[] + */ + public function getAwsAccountIds(): array + { + return $this->awsAccountIds ?? []; + } + + public function getLabel(): ?string + { + return $this->label; + } + + public function getQueueUrl(): ?string + { + return $this->queueUrl; + } + + /** + * @internal + */ + public function request(): Request + { + // Prepare headers + $headers = [ + 'Content-Type' => 'application/x-amz-json-1.0', + 'X-Amz-Target' => 'AmazonSQS.AddPermission', + 'Accept' => 'application/json', + ]; + + // Prepare query + $query = []; + + // Prepare URI + $uriString = '/'; + + // Prepare Body + $bodyPayload = $this->requestBody(); + $body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304); + + // Return the Request + return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body)); + } + + /** + * @param string[] $value + */ + public function setActions(array $value): self + { + $this->actions = $value; + + return $this; + } + + /** + * @param string[] $value + */ + public function setAwsAccountIds(array $value): self + { + $this->awsAccountIds = $value; + + return $this; + } + + public function setLabel(?string $value): self + { + $this->label = $value; + + return $this; + } + + public function setQueueUrl(?string $value): self + { + $this->queueUrl = $value; + + return $this; + } + + private function requestBody(): array + { + $payload = []; + if (null === $v = $this->queueUrl) { + throw new InvalidArgument(\sprintf('Missing parameter "QueueUrl" for "%s". The value cannot be null.', __CLASS__)); + } + $payload['QueueUrl'] = $v; + if (null === $v = $this->label) { + throw new InvalidArgument(\sprintf('Missing parameter "Label" for "%s". The value cannot be null.', __CLASS__)); + } + $payload['Label'] = $v; + if (null === $v = $this->awsAccountIds) { + throw new InvalidArgument(\sprintf('Missing parameter "AWSAccountIds" for "%s". The value cannot be null.', __CLASS__)); + } + + $index = -1; + $payload['AWSAccountIds'] = []; + foreach ($v as $listValue) { + ++$index; + $payload['AWSAccountIds'][$index] = $listValue; + } + + if (null === $v = $this->actions) { + throw new InvalidArgument(\sprintf('Missing parameter "Actions" for "%s". The value cannot be null.', __CLASS__)); + } + + $index = -1; + $payload['Actions'] = []; + foreach ($v as $listValue) { + ++$index; + $payload['Actions'][$index] = $listValue; + } + + return $payload; + } +} diff --git a/src/SqsClient.php b/src/SqsClient.php index 2d93069..aa839f6 100644 --- a/src/SqsClient.php +++ b/src/SqsClient.php @@ -38,6 +38,7 @@ use AsyncAws\Sqs\Exception\RequestThrottledException; use AsyncAws\Sqs\Exception\TooManyEntriesInBatchRequestException; use AsyncAws\Sqs\Exception\UnsupportedOperationException; +use AsyncAws\Sqs\Input\AddPermissionRequest; use AsyncAws\Sqs\Input\ChangeMessageVisibilityBatchRequest; use AsyncAws\Sqs\Input\ChangeMessageVisibilityRequest; use AsyncAws\Sqs\Input\CreateQueueRequest; @@ -69,6 +70,63 @@ class SqsClient extends AbstractApi { + /** + * Adds a permission to a queue for a specific principal [^1]. This allows sharing access to the queue. + * + * When you create a queue, you have full control access rights for the queue. Only you, the owner of the queue, can + * grant or deny permissions to the queue. For more information about these permissions, see Allow Developers to Write + * Messages to a Shared Queue [^2] in the *Amazon SQS Developer Guide*. + * + * > - `AddPermission` generates a policy for you. You can use `SetQueueAttributes` to upload your policy. For more + * > information, see Using Custom Policies with the Amazon SQS Access Policy Language [^3] in the *Amazon SQS + * > Developer Guide*. + * > - An Amazon SQS policy can have a maximum of seven actions per statement. + * > - To remove the ability to change queue permissions, you must deny permission to the `AddPermission`, + * > `RemovePermission`, and `SetQueueAttributes` actions in your IAM policy. + * > - Amazon SQS `AddPermission` does not support adding a non-account principal. + * > + * + * > Cross-account permissions don't apply to this action. For more information, see Grant cross-account permissions to + * > a role and a username [^4] in the *Amazon SQS Developer Guide*. + * + * [^1]: https://docs.aws.amazon.com/general/latest/gr/glos-chap.html#P + * [^2]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-writing-an-sqs-policy.html#write-messages-to-shared-queue + * [^3]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-creating-custom-policies.html + * [^4]: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name + * + * @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_AddPermission.html + * @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sqs-2012-11-05.html#addpermission + * + * @param array{ + * QueueUrl: string, + * Label: string, + * AWSAccountIds: string[], + * Actions: string[], + * '@region'?: string|null, + * }|AddPermissionRequest $input + * + * @throws OverLimitException + * @throws RequestThrottledException + * @throws QueueDoesNotExistException + * @throws InvalidAddressException + * @throws InvalidSecurityException + * @throws UnsupportedOperationException + */ + public function addPermission($input): Result + { + $input = AddPermissionRequest::create($input); + $response = $this->getResponse($input->request(), new RequestContext(['operation' => 'AddPermission', 'region' => $input->getRegion(), 'exceptionMapping' => [ + 'OverLimit' => OverLimitException::class, + 'RequestThrottled' => RequestThrottledException::class, + 'QueueDoesNotExist' => QueueDoesNotExistException::class, + 'InvalidAddress' => InvalidAddressException::class, + 'InvalidSecurity' => InvalidSecurityException::class, + 'UnsupportedOperation' => UnsupportedOperationException::class, + ]])); + + return new Result($response); + } + /** * Changes the visibility timeout of a specified message in a queue to a new value. The default visibility timeout for a * message is 30 seconds. The minimum is 0 seconds. The maximum is 12 hours. For more information, see Visibility diff --git a/tests/Integration/SqsClientTest.php b/tests/Integration/SqsClientTest.php index bbdb0f9..0857a46 100644 --- a/tests/Integration/SqsClientTest.php +++ b/tests/Integration/SqsClientTest.php @@ -5,6 +5,7 @@ use AsyncAws\Core\Credentials\NullProvider; use AsyncAws\Core\Test\TestCase; use AsyncAws\Sqs\Enum\QueueAttributeName; +use AsyncAws\Sqs\Input\AddPermissionRequest; use AsyncAws\Sqs\Input\ChangeMessageVisibilityBatchRequest; use AsyncAws\Sqs\Input\ChangeMessageVisibilityRequest; use AsyncAws\Sqs\Input\CreateQueueRequest; @@ -25,6 +26,21 @@ class SqsClientTest extends TestCase { + public function testAddPermission(): void + { + $client = $this->getClient(); + + $input = new AddPermissionRequest([ + 'QueueUrl' => 'change me', + 'Label' => 'change me', + 'AWSAccountIds' => ['change me'], + 'Actions' => ['change me'], + ]); + $result = $client->addPermission($input); + + $result->resolve(); + } + public function testChangeMessageVisibility() { $sqs = $this->getClient(); diff --git a/tests/Unit/Input/AddPermissionRequestTest.php b/tests/Unit/Input/AddPermissionRequestTest.php new file mode 100644 index 0000000..3f7f842 --- /dev/null +++ b/tests/Unit/Input/AddPermissionRequestTest.php @@ -0,0 +1,35 @@ + 'https://sqs.us-east-1.amazonaws.com/177715257436/MyQueue/', + 'Label' => 'MyLabel', + 'AWSAccountIds' => ['177715257436', '111111111111'], + 'Actions' => ['SendMessage', 'ReceiveMessage'], + ]); + + // see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_AddPermission.html + $expected = ' + POST / HTTP/1.0 + Content-Type: application/x-amz-json-1.0 + x-amz-target: AmazonSQS.AddPermission + Accept: application/json + + { + "QueueUrl": "https://sqs.us-east-1.amazonaws.com/177715257436/MyQueue/", + "Label": "MyLabel", + "Actions": ["SendMessage", "ReceiveMessage"], + "AWSAccountIds": ["177715257436", "111111111111"] + }'; + + self::assertRequestEqualsHttpRequest($expected, $input->request()); + } +} diff --git a/tests/Unit/SqsClientTest.php b/tests/Unit/SqsClientTest.php index 475fae9..d16c93d 100644 --- a/tests/Unit/SqsClientTest.php +++ b/tests/Unit/SqsClientTest.php @@ -5,6 +5,7 @@ use AsyncAws\Core\Credentials\NullProvider; use AsyncAws\Core\Result; use AsyncAws\Core\Test\TestCase; +use AsyncAws\Sqs\Input\AddPermissionRequest; use AsyncAws\Sqs\Input\ChangeMessageVisibilityBatchRequest; use AsyncAws\Sqs\Input\ChangeMessageVisibilityRequest; use AsyncAws\Sqs\Input\CreateQueueRequest; @@ -35,6 +36,22 @@ class SqsClientTest extends TestCase { + public function testAddPermission(): void + { + $client = new SqsClient([], new NullProvider(), new MockHttpClient()); + + $input = new AddPermissionRequest([ + 'QueueUrl' => 'change me', + 'Label' => 'change me', + 'AWSAccountIds' => ['change me'], + 'Actions' => ['change me'], + ]); + $result = $client->addPermission($input); + + self::assertInstanceOf(Result::class, $result); + self::assertFalse($result->info()['resolved']); + } + public function testChangeMessageVisibility(): void { $client = new SqsClient([], new NullProvider(), new MockHttpClient());