Skip to content

Commit a9eb49e

Browse files
authored
Merge pull request #704 from DirectoryTree/BUG-669
Bug 669 - [Bug] ldap_start_tls(): Unable to start TLS: Local error
2 parents 4f28001 + cd28b77 commit a9eb49e

File tree

7 files changed

+108
-14
lines changed

7 files changed

+108
-14
lines changed

src/Auth/Guard.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function bind(?string $username = null, ?string $password = null): void
8080
// Prior to binding, we will upgrade our connectivity to TLS on our current
8181
// connection and ensure we are not already bound before upgrading.
8282
// This is to prevent subsequent upgrading on several binds.
83-
if ($this->connection->isUsingTLS() && ! $this->connection->isBound()) {
83+
if ($this->connection->isUsingTLS() && ! $this->connection->isSecure()) {
8484
$this->connection->startTLS();
8585
}
8686

src/HandlesConnection.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ trait HandlesConnection
2323
protected mixed $connection = null;
2424

2525
/**
26-
* The bound status of the connection.
26+
* Whether the connection is bound.
2727
*/
2828
protected bool $bound = false;
2929

30+
/**
31+
* Whether the connection is secured over TLS or SSL.
32+
*/
33+
protected bool $secure = false;
34+
3035
/**
3136
* Whether the connection must be bound over SSL.
3237
*/
@@ -61,6 +66,14 @@ public function isBound(): bool
6166
return $this->bound;
6267
}
6368

69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function isSecure(): bool
73+
{
74+
return $this->secure;
75+
}
76+
6477
/**
6578
* {@inheritdoc}
6679
*/
@@ -139,6 +152,16 @@ public function getExtendedError(): ?string
139152
return $this->getDiagnosticMessage();
140153
}
141154

155+
/**
156+
* Handle the bind response.
157+
*/
158+
protected function handleBindResponse(LdapResultResponse $response): void
159+
{
160+
$this->bound = $response->successful();
161+
162+
$this->secure = $this->secure ?: $this->bound && $this->isUsingSSL();
163+
}
164+
142165
/**
143166
* Convert warnings to exceptions for the given operation.
144167
*

src/Ldap.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public function setRebindCallback(callable $callback): bool
150150
*/
151151
public function startTLS(): bool
152152
{
153-
return $this->executeFailableOperation(function () {
153+
return $this->secure = $this->executeFailableOperation(function () {
154154
return ldap_start_tls($this->connection);
155155
});
156156
}
@@ -276,7 +276,7 @@ public function bind(?string $dn = null, ?string $password = null, ?array $contr
276276

277277
$response = $this->parseResult($result);
278278

279-
$this->bound = $response && $response->successful();
279+
$this->handleBindResponse($response);
280280

281281
return $response;
282282
}

src/LdapInterface.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ public function isUsingTLS(): bool;
108108
*/
109109
public function isBound(): bool;
110110

111+
/**
112+
* Determine if the connection is secure over TLS or SSL.
113+
*/
114+
public function isSecure(): bool;
115+
111116
/**
112117
* Determine if the connection has been created.
113118
*/

src/Testing/LdapFake.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ public function getOption(int $option, mixed &$value = null): mixed
330330
*/
331331
public function startTLS(): bool
332332
{
333-
return $this->resolveExpectation(__FUNCTION__, func_get_args());
333+
return $this->secure = $this->resolveExpectation(__FUNCTION__, func_get_args());
334334
}
335335

336336
/**
@@ -368,7 +368,7 @@ public function bind(?string $dn = null, ?string $password = null, ?array $contr
368368
{
369369
$result = $this->resolveExpectation(__FUNCTION__, func_get_args());
370370

371-
$this->bound = $result->successful();
371+
$this->handleBindResponse($result);
372372

373373
return $result;
374374
}

tests/Unit/Auth/GuardTest.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function test_bind_does_not_rebind_with_configured_user()
5858

5959
$guard = new Guard($ldap, new DomainConfiguration());
6060

61-
$this->assertNull($guard->bind('user', 'pass'));
61+
$guard->bind('user', 'pass');
6262
}
6363

6464
public function test_bind_allows_null_username_and_password()
@@ -69,7 +69,7 @@ public function test_bind_allows_null_username_and_password()
6969

7070
$guard = new Guard($ldap, new DomainConfiguration());
7171

72-
$this->assertNull($guard->bind());
72+
$guard->bind();
7373
}
7474

7575
public function test_bind_always_throws_exception_on_invalid_credentials()
@@ -96,7 +96,7 @@ public function test_bind_as_administrator()
9696
'password' => 'bar',
9797
]));
9898

99-
$this->assertNull($guard->bindAsConfiguredUser());
99+
$guard->bindAsConfiguredUser();
100100
}
101101

102102
public function test_binding_events_are_fired_during_bind()
@@ -231,4 +231,25 @@ public function test_sasl_bind()
231231

232232
$this->assertTrue($ldap->isBound());
233233
}
234+
235+
public function test_tls_is_only_upgraded_once_on_subsequent_binds()
236+
{
237+
$ldap = (new LdapFake())->expect([
238+
LdapFake::operation('bind')->once()->with('admin', 'password')->andReturnResponse(),
239+
LdapFake::operation('startTLS')->once()->andReturnTrue(),
240+
LdapFake::operation('bind')->once()->with('foo', 'bar')->andReturnResponse(1),
241+
]);
242+
243+
$ldap->tls();
244+
245+
$this->assertFalse($ldap->isSecure());
246+
247+
$guard = new Guard($ldap, new DomainConfiguration([
248+
'username' => 'admin',
249+
'password' => 'password',
250+
]));
251+
252+
$this->assertFalse($guard->attempt('foo', 'bar'));
253+
$this->assertTrue($ldap->isSecure());
254+
}
234255
}

tests/Unit/LdapTest.php

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,10 @@ public function test_can_change_passwords()
8888

8989
public function test_set_options()
9090
{
91-
$ldap = (new LdapFake())
92-
->expect([
93-
LdapFake::operation('setOption')->once()->with(1, 'value')->andReturnTrue(),
94-
LdapFake::operation('setOption')->once()->with(2, 'value')->andReturnTrue(),
95-
]);
91+
$ldap = (new LdapFake())->expect([
92+
LdapFake::operation('setOption')->once()->with(1, 'value')->andReturnTrue(),
93+
LdapFake::operation('setOption')->once()->with(2, 'value')->andReturnTrue(),
94+
]);
9695

9796
$ldap->setOptions([1 => 'value', 2 => 'value']);
9897
}
@@ -105,4 +104,50 @@ public function test_get_detailed_error_returns_null_when_error_number_is_zero()
105104

106105
$this->assertNull($ldap->getDetailedError());
107106
}
107+
108+
public function test_is_secure_after_binding_with_an_ssl_connection()
109+
{
110+
$ldap = (new LdapFake())->expect([
111+
LdapFake::operation('bind')->once()->andReturnResponse(),
112+
]);
113+
114+
$ldap->ssl();
115+
116+
$this->assertFalse($ldap->isSecure());
117+
118+
$ldap->bind('foo', 'bar');
119+
120+
$this->assertTrue($ldap->isSecure());
121+
}
122+
123+
public function test_is_secure_after_starting_tls()
124+
{
125+
$ldap = (new LdapFake())->expect([
126+
LdapFake::operation('startTLS')->once()->andReturnTrue(),
127+
]);
128+
129+
$this->assertFalse($ldap->isSecure());
130+
131+
$ldap->startTLS();
132+
133+
$this->assertTrue($ldap->isSecure());
134+
}
135+
136+
public function test_is_secure_after_starting_tls_but_failing_bind()
137+
{
138+
$ldap = (new LdapFake())->expect([
139+
LdapFake::operation('startTLS')->once()->andReturnTrue(),
140+
LdapFake::operation('bind')->once()->andReturnResponse(1),
141+
]);
142+
143+
$this->assertFalse($ldap->isSecure());
144+
145+
$ldap->startTLS();
146+
147+
$this->assertTrue($ldap->isSecure());
148+
149+
$ldap->bind('foo', 'bar');
150+
151+
$this->assertTrue($ldap->isSecure());
152+
}
108153
}

0 commit comments

Comments
 (0)