From 6e1376a23b326149a7d9e342f0a3b509ef39b3b6 Mon Sep 17 00:00:00 2001 From: Jakub Adamus Date: Fri, 9 Feb 2024 12:10:29 +0100 Subject: [PATCH] Support updatePassword in lazy connections, reconnections and cloning --- .../Connection/AMQPStreamConnection.php | 8 +++++ PhpAmqpLib/Connection/AbstractConnection.php | 25 ++++++++++++-- demo/oauth2_authorization.php | 2 ++ .../Connection/OAuth2ConnectionTest.php | 34 +++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/PhpAmqpLib/Connection/AMQPStreamConnection.php b/PhpAmqpLib/Connection/AMQPStreamConnection.php index f9f5c377f..43622eb43 100644 --- a/PhpAmqpLib/Connection/AMQPStreamConnection.php +++ b/PhpAmqpLib/Connection/AMQPStreamConnection.php @@ -131,4 +131,12 @@ protected static function try_create_connection($host, $port, $user, $password, $channel_rpc_timeout ); } + + /** + * @param string $password + */ + protected function replace_password_in_construct_params($password) + { + $this->construct_params[3] = $password; + } } diff --git a/PhpAmqpLib/Connection/AbstractConnection.php b/PhpAmqpLib/Connection/AbstractConnection.php index aa3f7fdb3..95579fe6b 100644 --- a/PhpAmqpLib/Connection/AbstractConnection.php +++ b/PhpAmqpLib/Connection/AbstractConnection.php @@ -323,8 +323,29 @@ public function reconnect() */ public function updatePassword($password): void { - // send new secret to broker - $this->x_update_secret($password); + if ($this->login_method !== 'EXTERNAL') { + // password is always latest in response for PLAIN and AMQPPLAIN + // for EXTERNAL mechanism this has to be handled by calling user + $login_response_parts = explode("\0", $this->login_response); + $login_response_parts[count($login_response_parts) - 1] = $password; + $this->login_response = implode("\0", $login_response_parts); + } + + if ($this->is_connected) { + // send new secret to broker if currently connected + $this->x_update_secret($password); + } + + // for potential cloning + $this->replace_password_in_construct_params($password); + } + + /** + * @param string $password + */ + protected function replace_password_in_construct_params($password) + { + $this->construct_params[1] = $password; } /** diff --git a/demo/oauth2_authorization.php b/demo/oauth2_authorization.php index a9c4e1265..0a04d6758 100644 --- a/demo/oauth2_authorization.php +++ b/demo/oauth2_authorization.php @@ -30,6 +30,8 @@ function getNextOauth2Token() { pcntl_signal(SIGALRM, function () use ($connection) { echo "Refreshing token...\n"; $connection->updatePassword(getNextOauth2Token()); // this will fail on 2nd attempt - see getNextOauth2Token + sleep(1); + $connection->reconnect(); // reconnect should use new token too so this is working for lazy connections too! pcntl_alarm(5); }, true); pcntl_alarm(5); diff --git a/tests/Functional/Connection/OAuth2ConnectionTest.php b/tests/Functional/Connection/OAuth2ConnectionTest.php index 8bf67e067..bd87f7dd9 100644 --- a/tests/Functional/Connection/OAuth2ConnectionTest.php +++ b/tests/Functional/Connection/OAuth2ConnectionTest.php @@ -24,6 +24,40 @@ public function update_password() self::assertTrue($conn->isConnected()); } + /** + * @test + * @covers \PhpAmqpLib\Connection\AbstractConnection::updatePassword() + */ + public function update_password_should_replace_password_for_reconnect() + { + $conn = new AMQPStreamConnection(HOST, PORT, 'oauth', JWT_TOKEN_1, '/', false, 'PLAIN', null, 'en_US', 1); + $conn->updatePassword(JWT_TOKEN_2); + + $loginResponse = (new \ReflectionClass($conn))->getProperty('login_response'); + $loginResponse->setAccessible(true); + + self::assertStringContainsString(JWT_TOKEN_2, $loginResponse->getValue($conn)); + self::assertStringNotContainsString(JWT_TOKEN_1, $loginResponse->getValue($conn)); + } + + /** + * @test + * @covers \PhpAmqpLib\Connection\AbstractConnection::updatePassword() + * @covers \PhpAmqpLib\Connection\AbstractConnection::replace_password_in_construct_params() + * @covers \PhpAmqpLib\Connection\AMQPStreamConnection::replace_password_in_construct_params() + */ + public function update_password_should_replace_password_for_clone() + { + $conn = new AMQPStreamConnection(HOST, PORT, 'oauth', JWT_TOKEN_1, '/', false, 'PLAIN', null, 'en_US', 1); + $conn->updatePassword(JWT_TOKEN_2); + + $constructorParams = (new \ReflectionClass($conn))->getProperty('construct_params'); + $constructorParams->setAccessible(true); + + self::assertContains(JWT_TOKEN_2, $constructorParams->getValue($conn)); + self::assertNotContains(JWT_TOKEN_1, $constructorParams->getValue($conn)); + } + /** * @test * @covers \PhpAmqpLib\Connection\AbstractConnection::x_update_secret()