Skip to content

Commit 2db4982

Browse files
authored
Merge pull request #104 from neo4j-php/ssl
Created SSL configuration override at driver level
2 parents 7eeaaf2 + 7757f58 commit 2db4982

14 files changed

+457
-90
lines changed

src/Bolt/BoltConnectionPool.php

Lines changed: 65 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717
use Bolt\Bolt;
1818
use Bolt\connection\StreamSocket;
1919
use Exception;
20-
use function explode;
21-
use const FILTER_VALIDATE_IP;
22-
use function filter_var;
2320
use Laudis\Neo4j\Common\BoltConnection;
2421
use Laudis\Neo4j\Contracts\AuthenticateInterface;
2522
use Laudis\Neo4j\Contracts\ConnectionInterface;
2623
use Laudis\Neo4j\Contracts\ConnectionPoolInterface;
2724
use Laudis\Neo4j\Databags\DatabaseInfo;
25+
use Laudis\Neo4j\Databags\DriverConfiguration;
2826
use Laudis\Neo4j\Databags\SessionConfiguration;
2927
use Laudis\Neo4j\Enum\ConnectionProtocol;
3028
use Laudis\Neo4j\Neo4j\RoutingTable;
@@ -39,8 +37,19 @@
3937
*/
4038
final class BoltConnectionPool implements ConnectionPoolInterface
4139
{
42-
/** @var array<string, list<ConnectionInterface<Bolt>>> */
40+
/** @var array<string, list<BoltConnection>> */
4341
private static array $connectionCache = [];
42+
private DriverConfiguration $driverConfig;
43+
private SslConfigurator $sslConfigurator;
44+
45+
/**
46+
* @psalm-external-mutation-free
47+
*/
48+
public function __construct(DriverConfiguration $driverConfig, SslConfigurator $sslConfigurator)
49+
{
50+
$this->driverConfig = $driverConfig;
51+
$this->sslConfigurator = $sslConfigurator;
52+
}
4453

4554
/**
4655
* @throws Exception
@@ -60,8 +69,20 @@ public function acquire(
6069
self::$connectionCache[$key] = [];
6170
}
6271

63-
foreach (self::$connectionCache[$key] as $connection) {
72+
foreach (self::$connectionCache[$key] as $i => $connection) {
6473
if (!$connection->isOpen()) {
74+
$sslConfig = $connection->getDriverConfiguration()->getSslConfiguration();
75+
$newSslConfig = $this->driverConfig->getSslConfiguration();
76+
if ($sslConfig->getMode() !== $newSslConfig->getMode() ||
77+
$sslConfig->isVerifyPeer() === $newSslConfig->isVerifyPeer()
78+
) {
79+
$connection = $this->openConnection($connectingTo, $socketTimeout, $uri, $table, $authenticate, $userAgent, $config);
80+
81+
/** @psalm-suppress PropertyTypeCoercion */
82+
self::$connectionCache[$key][$i] = $connection;
83+
84+
return $connection;
85+
}
6586
$connection->open();
6687

6788
$authenticate->authenticateBolt($connection->getImplementation(), $connectingTo, $userAgent);
@@ -70,9 +91,42 @@ public function acquire(
7091
}
7192
}
7293

94+
$connection = $this->openConnection($connectingTo, $socketTimeout, $uri, $table, $authenticate, $userAgent, $config);
95+
96+
self::$connectionCache[$key][] = $connection;
97+
98+
return $connection;
99+
}
100+
101+
public function canConnect(UriInterface $uri, AuthenticateInterface $authenticate, ?RoutingTable $table = null, ?UriInterface $server = null): bool
102+
{
103+
$connectingTo = $server ?? $uri;
104+
$socket = new StreamSocket($uri->getHost(), $connectingTo->getPort() ?? 7687);
105+
106+
$this->setupSsl($uri, $connectingTo, $table, $socket);
107+
108+
try {
109+
$bolt = new Bolt($socket);
110+
$authenticate->authenticateBolt($bolt, $connectingTo, 'ping');
111+
} catch (Throwable $e) {
112+
return false;
113+
}
114+
115+
return true;
116+
}
117+
118+
private function openConnection(
119+
UriInterface $connectingTo,
120+
float $socketTimeout,
121+
UriInterface $uri,
122+
?RoutingTable $table,
123+
AuthenticateInterface $authenticate,
124+
string $userAgent,
125+
SessionConfiguration $config
126+
): BoltConnection {
73127
$socket = new StreamSocket($connectingTo->getHost(), $connectingTo->getPort() ?? 7687, $socketTimeout);
74128

75-
$this->configureSsl($uri, $connectingTo, $socket, $table);
129+
$this->setupSsl($uri, $connectingTo, $table, $socket);
76130

77131
$bolt = new Bolt($socket);
78132
$authenticate->authenticateBolt($bolt, $connectingTo, $userAgent);
@@ -104,6 +158,7 @@ public function acquire(
104158
ConnectionProtocol::determineBoltVersion($bolt),
105159
$config->getAccessMode(),
106160
new DatabaseInfo($config->getDatabase()),
161+
$this->driverConfig,
107162
static function () use ($socket, $authenticate, $connectingTo, $userAgent, $originalBolt) {
108163
$bolt = $originalBolt->get();
109164
if ($bolt === null) {
@@ -117,61 +172,14 @@ static function () use ($socket, $authenticate, $connectingTo, $userAgent, $orig
117172

118173
$connection->open();
119174

120-
self::$connectionCache[$key][] = $connection;
121-
122175
return $connection;
123176
}
124177

125-
private function configureSsl(UriInterface $uri, UriInterface $server, StreamSocket $socket, ?RoutingTable $table): void
178+
private function setupSsl(UriInterface $uri, UriInterface $connectingTo, ?RoutingTable $table, StreamSocket $socket): void
126179
{
127-
$scheme = $uri->getScheme();
128-
$explosion = explode('+', $scheme, 2);
129-
$sslConfig = $explosion[1] ?? '';
130-
131-
if (str_starts_with($sslConfig, 's')) {
132-
// We have to pass a different host when working with ssl on aura.
133-
// There is a strange behaviour where if we pass the uri host on a single
134-
// instance aura deployment, we need to pass the original uri for the
135-
// ssl configuration to be valid.
136-
if ($table && count($table->getWithRole()) > 1) {
137-
$this->enableSsl($server->getHost(), $sslConfig, $socket);
138-
} else {
139-
$this->enableSsl($uri->getHost(), $sslConfig, $socket);
140-
}
180+
$config = $this->sslConfigurator->configure($uri, $connectingTo, $table, $this->driverConfig);
181+
if ($config !== null) {
182+
$socket->setSslContextOptions($config);
141183
}
142184
}
143-
144-
private function enableSsl(string $host, string $sslConfig, StreamSocket $sock): void
145-
{
146-
$options = [
147-
'verify_peer' => true,
148-
'peer_name' => $host,
149-
];
150-
if (!filter_var($host, FILTER_VALIDATE_IP)) {
151-
$options['SNI_enabled'] = true;
152-
}
153-
if ($sslConfig === 's') {
154-
$sock->setSslContextOptions($options);
155-
} elseif ($sslConfig === 'ssc') {
156-
$options['allow_self_signed'] = true;
157-
$sock->setSslContextOptions($options);
158-
}
159-
}
160-
161-
public function canConnect(UriInterface $uri, AuthenticateInterface $authenticate, ?RoutingTable $table = null, ?UriInterface $server = null): bool
162-
{
163-
$connectingTo = $server ?? $uri;
164-
$socket = new StreamSocket($uri->getHost(), $connectingTo->getPort() ?? 7687);
165-
166-
$this->configureSsl($uri, $connectingTo, $socket, $table);
167-
168-
try {
169-
$bolt = new Bolt($socket);
170-
$authenticate->authenticateBolt($bolt, $connectingTo, 'ping');
171-
} catch (Throwable $e) {
172-
return false;
173-
}
174-
175-
return true;
176-
}
177185
}

src/Bolt/BoltDriver.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,14 @@ public static function create($uri, ?DriverConfiguration $configuration = null,
9191
}
9292

9393
$socketTimeout ??= TransactionConfiguration::DEFAULT_TIMEOUT;
94+
$configuration ??= DriverConfiguration::default();
9495

9596
if ($formatter !== null) {
9697
return new self(
9798
$uri,
9899
$authenticate ?? Authenticate::fromUrl(),
99-
new BoltConnectionPool(),
100-
$configuration ?? DriverConfiguration::default(),
100+
new BoltConnectionPool($configuration, new SslConfigurator()),
101+
$configuration,
101102
$formatter,
102103
$socketTimeout
103104
);
@@ -106,8 +107,8 @@ public static function create($uri, ?DriverConfiguration $configuration = null,
106107
return new self(
107108
$uri,
108109
$authenticate ?? Authenticate::fromUrl(),
109-
new BoltConnectionPool(),
110-
$configuration ?? DriverConfiguration::default(),
110+
new BoltConnectionPool($configuration, new SslConfigurator()),
111+
$configuration,
111112
OGMFormatter::create(),
112113
$socketTimeout
113114
);

src/Bolt/SslConfigurator.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Laudis Neo4j package.
5+
*
6+
* (c) Laudis technologies <http://laudis.tech>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Laudis\Neo4j\Bolt;
13+
14+
use function count;
15+
use function explode;
16+
use const FILTER_VALIDATE_IP;
17+
use function filter_var;
18+
use Laudis\Neo4j\Databags\DriverConfiguration;
19+
use Laudis\Neo4j\Enum\SslMode;
20+
use Laudis\Neo4j\Neo4j\RoutingTable;
21+
use Psr\Http\Message\UriInterface;
22+
23+
final class SslConfigurator
24+
{
25+
public function configure(UriInterface $uri, UriInterface $server, ?RoutingTable $table, DriverConfiguration $config): ?array
26+
{
27+
$sslMode = $config->getSslConfiguration()->getMode();
28+
$sslConfig = '';
29+
if ($sslMode === SslMode::FROM_URL()) {
30+
$scheme = $uri->getScheme();
31+
$explosion = explode('+', $scheme, 2);
32+
$sslConfig = $explosion[1] ?? '';
33+
} elseif ($sslMode === SslMode::ENABLE()) {
34+
$sslConfig = 's';
35+
} elseif ($sslMode === SslMode::ENABLE_WITH_SELF_SIGNED()) {
36+
$sslConfig = 'ssc';
37+
}
38+
39+
if (str_starts_with($sslConfig, 's')) {
40+
// We have to pass a different host when working with ssl on aura.
41+
// There is a strange behaviour where if we pass the uri host on a single
42+
// instance aura deployment, we need to pass the original uri for the
43+
// ssl configuration to be valid.
44+
if ($table && count($table->getWithRole()) > 1) {
45+
return $this->enableSsl($server->getHost(), $sslConfig, $config);
46+
}
47+
48+
return $this->enableSsl($uri->getHost(), $sslConfig, $config);
49+
}
50+
51+
return null;
52+
}
53+
54+
private function enableSsl(string $host, string $sslConfig, DriverConfiguration $config): ?array
55+
{
56+
$options = [
57+
'verify_peer' => $config->getSslConfiguration()->isVerifyPeer(),
58+
'peer_name' => $host,
59+
];
60+
if (!filter_var($host, FILTER_VALIDATE_IP)) {
61+
$options['SNI_enabled'] = true;
62+
}
63+
if ($sslConfig === 's') {
64+
return $options;
65+
}
66+
67+
if ($sslConfig === 'ssc') {
68+
$options['allow_self_signed'] = true;
69+
70+
return $options;
71+
}
72+
73+
return null;
74+
}
75+
}

src/Common/BoltConnection.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use function call_user_func;
1818
use Laudis\Neo4j\Contracts\ConnectionInterface;
1919
use Laudis\Neo4j\Databags\DatabaseInfo;
20+
use Laudis\Neo4j\Databags\DriverConfiguration;
2021
use Laudis\Neo4j\Enum\AccessMode;
2122
use Laudis\Neo4j\Enum\ConnectionProtocol;
2223
use Psr\Http\Message\UriInterface;
@@ -45,6 +46,8 @@ final class BoltConnection implements ConnectionInterface
4546
* @psalm-readonly
4647
*/
4748
private $connector;
49+
/** @psalm-readonly */
50+
private DriverConfiguration $driverConfiguration;
4851

4952
/**
5053
* @psalm-mutation-free
@@ -58,6 +61,7 @@ public function __construct(
5861
ConnectionProtocol $protocol,
5962
AccessMode $accessMode,
6063
DatabaseInfo $databaseInfo,
64+
DriverConfiguration $config,
6165
$connector
6266
) {
6367
$this->serverAgent = $serverAgent;
@@ -67,6 +71,7 @@ public function __construct(
6771
$this->accessMode = $accessMode;
6872
$this->databaseInfo = $databaseInfo;
6973
$this->connector = $connector;
74+
$this->driverConfiguration = $config;
7075
}
7176

7277
/**
@@ -148,4 +153,12 @@ public function close(): void
148153
{
149154
$this->bolt = null;
150155
}
156+
157+
/**
158+
* @psalm-mutation-free
159+
*/
160+
public function getDriverConfiguration(): DriverConfiguration
161+
{
162+
return $this->driverConfiguration;
163+
}
151164
}

0 commit comments

Comments
 (0)