Skip to content
Merged
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CONNECTIONS=bolt://neo4j:testtest@neo4j,http://neo4j:testtest@neo4j,bolt://neo4j:testtest@core1
PHP_VERSION=8.1
5 changes: 1 addition & 4 deletions .github/workflows/integration-test-cluster-neo4j-4.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@ jobs:
strategy:
matrix:
php: ['8.1.31', '8.3.17']
env:
PHP_VERSION: ${{ matrix.php }}
CONNECTION: neo4j://neo4j:testtest@localhost:7688
name: "Running on PHP ${{ matrix.php }} in a Neo4j 4.4 cluster"

steps:
- uses: actions/checkout@v4
- name: Populate .env
run: |
echo "PHP_VERSION=${{ matrix.php }}" > .env
echo "CONNECTION=neo4j://neo4j:testtest@neo4j" >> .env
echo "CONNECTION=neo4j://neo4j:testtest@core1" >> .env
- uses: hoverkraft-tech/[email protected]
name: Start services
with:
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/integration-test-cluster-neo4j-5.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@ jobs:
strategy:
matrix:
php: ['8.1.31', '8.3.17']
env:
PHP_VERSION: ${{ matrix.php }}
CONNECTION: neo4j://neo4j:testtest@localhost:7687
name: "Running on PHP ${{ matrix.php }} with a Neo4j 5.20-enterprise cluster"

steps:
- uses: actions/checkout@v4
- name: Populate .env
run: |
echo "PHP_VERSION=${{ matrix.php }}" > .env
echo "CONNECTION=neo4j://neo4j:testtest@neo4j" >> .env
echo "CONNECTION=neo4j://neo4j:testtest@server1" >> .env
- uses: hoverkraft-tech/[email protected]
name: Start services
with:
Expand Down
10 changes: 2 additions & 8 deletions src/Authentication/Authenticate.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ final class Authenticate
*/
public static function basic(string $username, string $password, ?Neo4jLogger $logger = null): BasicAuth
{
/** @psalm-suppress ImpureMethodCall Uri is a pure object */

return new BasicAuth($username, $password, $logger);
}

/**
* Authenticate using a kerberos token.
*
* @pure
*/
public static function kerberos(string $token, ?Neo4jLogger $logger = null): KerberosAuth
{
Expand All @@ -50,8 +50,6 @@ public static function kerberos(string $token, ?Neo4jLogger $logger = null): Ker

/**
* Authenticate using a OpenID Connect token.
*
* @pure
*/
public static function oidc(string $token, ?Neo4jLogger $logger = null): OpenIDConnectAuth
{
Expand All @@ -60,8 +58,6 @@ public static function oidc(string $token, ?Neo4jLogger $logger = null): OpenIDC

/**
* Don't authenticate at all.
*
* @pure
*/
public static function disabled(?Neo4jLogger $logger = null): NoAuth
{
Expand All @@ -70,8 +66,6 @@ public static function disabled(?Neo4jLogger $logger = null): NoAuth

/**
* Authenticate from information found in the url.
*
* @pure
*/
public static function fromUrl(UriInterface $uri, ?Neo4jLogger $logger = null): AuthenticateInterface
{
Expand Down
61 changes: 41 additions & 20 deletions src/Authentication/BasicAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,17 @@
use Bolt\protocol\V5_3;
use Bolt\protocol\V5_4;
use Exception;
use Laudis\Neo4j\Bolt\BoltMessageFactory;
use Laudis\Neo4j\Common\Neo4jLogger;
use Laudis\Neo4j\Common\ResponseHelper;
use Laudis\Neo4j\Contracts\AuthenticateInterface;
use Psr\Http\Message\UriInterface;
use Psr\Log\LogLevel;

/**
* Authenticates connections using a basic username and password.
*/
final class BasicAuth implements AuthenticateInterface
{
/**
* @psalm-external-mutation-free
*/
public function __construct(
private readonly string $username,
private readonly string $password,
Expand All @@ -48,36 +45,60 @@ public function __construct(
*/
public function authenticateBolt(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol, string $userAgent): array
{
$factory = $this->createMessageFactory($protocol);

if (method_exists($protocol, 'logon')) {
$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent]);
$protocol->hello(['user_agent' => $userAgent]);
$helloMetadata = ['user_agent' => $userAgent];

$factory->createHelloMessage($helloMetadata)->send();
$response = ResponseHelper::getResponse($protocol);
$this->logger?->log(LogLevel::DEBUG, 'LOGON', ['scheme' => 'basic', 'principal' => $this->username]);
$protocol->logon([

$credentials = [
'scheme' => 'basic',
'principal' => $this->username,
'credentials' => $this->password,
]);
];

$factory->createLogonMessage($credentials)->send();
ResponseHelper::getResponse($protocol);

/** @var array{server: string, connection_id: string, hints: list} */
return $response->content;
} else {
$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent, 'scheme' => 'basic', 'principal' => $this->username]);
$protocol->hello([
'user_agent' => $userAgent,
'scheme' => 'basic',
'principal' => $this->username,
'credentials' => $this->password,
]);

/** @var array{server: string, connection_id: string, hints: list} */
return ResponseHelper::getResponse($protocol)->content;
}

$helloMetadata = [
'user_agent' => $userAgent,
'scheme' => 'basic',
'principal' => $this->username,
'credentials' => $this->password,
];

$factory->createHelloMessage($helloMetadata)->send();

/** @var array{server: string, connection_id: string, hints: list} */
return ResponseHelper::getResponse($protocol)->content;
}

/**
* @throws Exception
*/
public function logoff(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol): void
{
$factory = $this->createMessageFactory($protocol);
$factory->createLogoffMessage()->send();
ResponseHelper::getResponse($protocol);
}

public function toString(UriInterface $uri): string
{
return sprintf('Basic %s:%s@%s:%s', $this->username, '######', $uri->getHost(), $uri->getPort() ?? '');
}

/**
* Helper to create message factory.
*/
private function createMessageFactory(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol): BoltMessageFactory
{
return new BoltMessageFactory($protocol, $this->logger);
}
}
65 changes: 31 additions & 34 deletions src/Authentication/KerberosAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Bolt\protocol\V5_3;
use Bolt\protocol\V5_4;
use Exception;
use Laudis\Neo4j\Bolt\BoltMessageFactory;
use Laudis\Neo4j\Common\Neo4jLogger;
use Laudis\Neo4j\Common\ResponseHelper;
use Laudis\Neo4j\Contracts\AuthenticateInterface;
Expand All @@ -34,9 +35,6 @@
*/
final class KerberosAuth implements AuthenticateInterface
{
/**
* @psalm-external-mutation-free
*/
public function __construct(
private readonly string $token,
private readonly ?Neo4jLogger $logger,
Expand All @@ -47,11 +45,6 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s
{
$this->logger?->log(LogLevel::DEBUG, 'Authenticating using KerberosAuth');

/**
* @psalm-suppress ImpureMethodCall Request is a pure object:
*
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message-meta.md#why-value-objects
*/
return $request->withHeader('Authorization', 'Kerberos '.$this->token)
->withHeader('User-Agent', $userAgent);
}
Expand All @@ -63,36 +56,40 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s
*/
public function authenticateBolt(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol, string $userAgent): array
{
if (method_exists($protocol, 'logon')) {
$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent]);
$protocol->hello(['user_agent' => $userAgent]);
$response = ResponseHelper::getResponse($protocol);
$this->logger?->log(LogLevel::DEBUG, 'LOGON', ['scheme' => 'kerberos', 'principal' => '']);
$protocol->logon([
'scheme' => 'kerberos',
'principal' => '',
'credentials' => $this->token,
]);
ResponseHelper::getResponse($protocol);

/** @var array{server: string, connection_id: string, hints: list} */
return $response->content;
} else {
$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent, 'scheme' => 'kerberos', 'principal' => '']);
$protocol->hello([
'user_agent' => $userAgent,
'scheme' => 'kerberos',
'principal' => '',
'credentials' => $this->token,
]);

/** @var array{server: string, connection_id: string, hints: list} */
return ResponseHelper::getResponse($protocol)->content;
}
$factory = $this->createMessageFactory($protocol);

$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent]);

$factory->createHelloMessage(['user_agent' => $userAgent])->send();

$response = ResponseHelper::getResponse($protocol);

$this->logger?->log(LogLevel::DEBUG, 'LOGON', ['scheme' => 'kerberos', 'principal' => '']);

$factory->createLogonMessage([
'scheme' => 'kerberos',
'principal' => '',
'credentials' => $this->token,
])->send();

ResponseHelper::getResponse($protocol);

/**
* @var array{server: string, connection_id: string, hints: list}
*/
return $response->content;
}

public function toString(UriInterface $uri): string
{
return sprintf('Kerberos %s@%s:%s', $this->token, $uri->getHost(), $uri->getPort() ?? '');
}

/**
* Helper to create the message factory.
*/
private function createMessageFactory(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol): BoltMessageFactory
{
return new BoltMessageFactory($protocol, $this->logger);
}
}
56 changes: 30 additions & 26 deletions src/Authentication/NoAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Bolt\protocol\V5_3;
use Bolt\protocol\V5_4;
use Exception;
use Laudis\Neo4j\Bolt\BoltMessageFactory;
use Laudis\Neo4j\Common\Neo4jLogger;
use Laudis\Neo4j\Common\ResponseHelper;
use Laudis\Neo4j\Contracts\AuthenticateInterface;
Expand All @@ -29,14 +30,8 @@

use function sprintf;

/**
* Doesn't authenticate connections.
*/
final class NoAuth implements AuthenticateInterface
{
/**
* @pure
*/
public function __construct(
private readonly ?Neo4jLogger $logger,
) {
Expand All @@ -46,11 +41,6 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s
{
$this->logger?->log(LogLevel::DEBUG, 'Authentication disabled');

/**
* @psalm-suppress ImpureMethodCall Request is a pure object:
*
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message-meta.md#why-value-objects
*/
return $request->withHeader('User-Agent', $userAgent);
}

Expand All @@ -61,32 +51,46 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s
*/
public function authenticateBolt(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol, string $userAgent): array
{
$factory = $this->createMessageFactory($protocol);

if (method_exists($protocol, 'logon')) {
$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent]);
$protocol->hello(['user_agent' => $userAgent]);
$helloMetadata = ['user_agent' => $userAgent];

$factory->createHelloMessage($helloMetadata)->send();
$response = ResponseHelper::getResponse($protocol);
$this->logger?->log(LogLevel::DEBUG, 'LOGON', ['scheme' => 'none']);
$protocol->logon([
'scheme' => 'none',
]);

$factory->createLogonMessage(['scheme' => 'none'])->send();
ResponseHelper::getResponse($protocol);

/** @var array{server: string, connection_id: string, hints: list} */
return $response->content;
} else {
$this->logger?->log(LogLevel::DEBUG, 'HELLO', ['user_agent' => $userAgent, 'scheme' => 'none']);
$protocol->hello([
'user_agent' => $userAgent,
'scheme' => 'none',
]);

/** @var array{server: string, connection_id: string, hints: list} */
return ResponseHelper::getResponse($protocol)->content;
}

$helloMetadata = [
'user_agent' => $userAgent,
'scheme' => 'none',
];

$factory->createHelloMessage($helloMetadata)->send();

/** @var array{server: string, connection_id: string, hints: list} */
return ResponseHelper::getResponse($protocol)->content;
}

public function logoff(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol): void
{
$factory = $this->createMessageFactory($protocol);
$factory->createLogoffMessage()->send();
ResponseHelper::getResponse($protocol);
}

public function toString(UriInterface $uri): string
{
return sprintf('No Auth %s:%s', $uri->getHost(), $uri->getPort() ?? '');
}

private function createMessageFactory(V4_4|V5|V5_1|V5_2|V5_3|V5_4 $protocol): BoltMessageFactory
{
return new BoltMessageFactory($protocol, $this->logger);
}
}
Loading