Skip to content

Commit 0f9bc91

Browse files
committed
Support authentication with URL-encoded special characters
1 parent bf10b89 commit 0f9bc91

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ $factory = new Clue\React\Ami\Factory($loop, $connector);
121121

122122
The `createClient(string $url): PromiseInterface<Client,Exception>` method can be used to
123123
create a new [`Client`](#client).
124+
124125
It helps with establishing a plain TCP/IP or secure TLS connection to the AMI
125126
and optionally issuing an initial `login` action.
126127

@@ -154,6 +155,18 @@ to pass a username and secret for your AMI login details like this:
154155
$factory->createClient('user:secret@localhost');
155156
```
156157

158+
Note that both the username and password must be URL-encoded (percent-encoded)
159+
if they contain special characters:
160+
161+
```php
162+
$user = 'he:llo';
163+
$pass = 'p@ss';
164+
165+
$promise = $factory->createClient(
166+
rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost'
167+
);
168+
```
169+
157170
The `Factory` defaults to establishing a plaintext TCP connection.
158171
If you want to create a secure TLS connection, you can use the `tls` scheme
159172
(which defaults to port `5039`):

src/Factory.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector =
5252

5353
/**
5454
* Create a new [`Client`](#client).
55+
*
5556
* It helps with establishing a plain TCP/IP or secure TLS connection to the AMI
5657
* and optionally issuing an initial `login` action.
5758
*
@@ -85,6 +86,18 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector =
8586
* $factory->createClient('user:secret@localhost');
8687
* ```
8788
*
89+
* Note that both the username and password must be URL-encoded (percent-encoded)
90+
* if they contain special characters:
91+
*
92+
* ```php
93+
* $user = 'he:llo';
94+
* $pass = 'p@ss';
95+
*
96+
* $promise = $factory->createClient(
97+
* rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost'
98+
* );
99+
* ```
100+
*
88101
* The `Factory` defaults to establishing a plaintext TCP connection.
89102
* If you want to create a secure TLS connection, you can use the `tls` scheme
90103
* (which defaults to port `5039`):
@@ -116,7 +129,7 @@ public function createClient($url)
116129
$promise = $promise->then(function (Client $client) use ($parts) {
117130
$sender = new ActionSender($client);
118131

119-
return $sender->login($parts['user'], $parts['pass'])->then(
132+
return $sender->login(rawurldecode($parts['user']), rawurldecode($parts['pass']))->then(
120133
function ($response) use ($client) {
121134
return $client;
122135
},

tests/FactoryTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,49 @@ public function testCreateClientUsesTlsConnectorWithTlsLocation()
5454
$this->factory->createClient('tls://ami.local:1234');
5555
}
5656

57+
public function testCreateClientResolvesWithClientWhenConnectionResolves()
58+
{
59+
$connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
60+
$this->tcp->expects($this->once())->method('connect')->willReturn(\React\Promise\resolve($connection));
61+
62+
$promise = $this->factory->createClient('localhost');
63+
64+
$client = null;
65+
$promise->then(function ($value) use (&$client) {
66+
$client = $value;
67+
});
68+
69+
$this->assertInstanceOf('Clue\React\Ami\Client', $client);
70+
}
71+
72+
public function testCreateClientWithAuthenticationWillSendLoginActionWithDecodedUserInfo()
73+
{
74+
$promiseAuthenticated = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
75+
76+
$clientConnected = null;
77+
$promiseClient = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
78+
$promiseClient->expects($this->once())->method('then')->with($this->callback(function ($callback) use (&$clientConnected) {
79+
$clientConnected = $callback;
80+
return true;
81+
}))->willReturn($promiseAuthenticated);
82+
83+
$promiseConnecting = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
84+
$promiseConnecting->expects($this->once())->method('then')->willReturn($promiseClient);
85+
$this->tcp->expects($this->once())->method('connect')->willReturn($promiseConnecting);
86+
87+
$action = $this->getMockBuilder('Clue\React\Ami\Protocol\Action')->getMock();
88+
$client = $this->getMockBuilder('Clue\React\Ami\Client')->disableOriginalConstructor()->getMock();
89+
$client->expects($this->once())->method('createAction')->with('Login', array('UserName' => 'user@host', 'Secret' => 'pass+word!', 'Events' => null))->willReturn($action);
90+
$client->expects($this->once())->method('request')->with($action)->willReturn($promiseAuthenticated);
91+
92+
$promise = $this->factory->createClient('user%40host:pass+word%21@localhost');
93+
94+
$this->assertSame($promiseAuthenticated, $promise);
95+
96+
$this->assertNotNull($clientConnected);
97+
$clientConnected($client);
98+
}
99+
57100
public function testCreateClientWithInvalidUrlWillRejectPromise()
58101
{
59102
$promise = $this->factory->createClient('///');

0 commit comments

Comments
 (0)