diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
index a808734..187fb35 100644
--- a/.github/workflows/coding-standards.yml
+++ b/.github/workflows/coding-standards.yml
@@ -30,4 +30,5 @@ jobs:
uses: "ramsey/composer-install@v1"
- name: "Run PHP_CodeSniffer"
- run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"
+ run: |
+ vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index f422cee..75325a2 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -50,18 +50,33 @@ jobs:
services:
ldap:
- image: bitnami/openldap
+ image: bitnami/openldap:latest
ports:
- - 3389:3389
+ - 1389:1389
+ - 1636:1636
env:
LDAP_ADMIN_USERNAME: admin
LDAP_ADMIN_PASSWORD: a_great_password
LDAP_ROOT: dc=local,dc=com
- LDAP_PORT_NUMBER: 3389
+ LDAP_PORT_NUMBER: 1389
LDAP_USERS: a
LDAP_PASSWORDS: a
+ LDAP_ENABLE_TLS: yes
+ LDAP_LDAPS_PORT_NUMBER: 1636
+ LDAP_TLS_VERIFY_CLIENT: try
+ LDAP_TLS_CERT_FILE: /opt/bitnami/openldap/certs/openldap.crt
+ LDAP_TLS_KEY_FILE: /opt/bitnami/openldap/certs/openldap.key
+ LDAP_TLS_CA_FILE: /opt/bitnami/openldap/certs/openldapCA.crt
+ volumes:
+ - ${{ github.workspace }}/Tests/certs:/opt/bitnami/openldap/certs
+ options: --name=ldaprecord
steps:
+ # Required as ./Tests/certs is created during ldap service build.
+ - name: "Make current user owner of workspace"
+ run: |
+ sudo chown -R $USER:$USER ${{ github.workspace }}
+
- name: "Checkout"
uses: "actions/checkout@v2"
@@ -73,8 +88,17 @@ jobs:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1"
+ - name: "Generate certificates"
+ run: |
+ composer generate-certs
+
+ - name: "Restart Docker"
+ run: |
+ docker restart ldaprecord
+
- name: "Validate composer files"
- run: "composer validate --strict"
+ run: |
+ composer validate --strict
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v2"
@@ -82,8 +106,12 @@ jobs:
path: "~/.composer/cache"
key: php-${{ matrix.php-version }}-symfony-${{ matrix.symfony-require }}-composer-locked-${{ hashFiles('composer.lock') }}
restore-keys: |
+ php-${{ matrix.php-version }}-symfony-${{ matrix.symfony-require }}-composer-locked-${{ hashFiles('composer.lock') }}
php-${{ matrix.php-version }}-symfony-${{ matrix.symfony-require }}-composer-locked-
+ php-${{ matrix.php-version }}-symfony-${{ matrix.symfony-require }}
+ php-${{ matrix.php-version }}-symfony-
php-${{ matrix.php-version }}-
+ php-
- name: "Install dependencies with composer"
env:
@@ -93,4 +121,5 @@ jobs:
composer update --no-interaction --no-progress ${{ matrix.composer-flags }}
- name: "Run PHPUnit"
- run: "vendor/bin/phpunit"
+ run: |
+ composer phpunit
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 703ff97..ecf01dd 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -30,7 +30,9 @@ jobs:
uses: "ramsey/composer-install@v1"
- name: "Run a static analysis with phpstan/phpstan"
- run: "vendor/bin/phpstan analyse"
+ run: |
+ vendor/bin/phpstan analyse
- name: "Run a static analysis with vimeo/psalm"
- run: "vendor/bin/psalm --output-format=github"
+ run: |
+ vendor/bin/psalm --output-format=github
diff --git a/.gitignore b/.gitignore
index bcb722c..d560bc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,8 @@ package.tar
/.psalm/
/.idea/
/.phpcs-cache
+#/Tests/certs/*.crt
+/Tests/certs/*.csr
+#/Tests/certs/*.key
+/Tests/certs/*.srl
+
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 9fa95e2..61efd26 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -59,6 +59,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultFalse()
->end()
->arrayNode('options')
+ ->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('name')->end()
diff --git a/Tests/DependencyInjection/Iter8LdapRecordExtensionTest.php b/Tests/DependencyInjection/Iter8LdapRecordExtensionTest.php
index 551def2..9d9188f 100644
--- a/Tests/DependencyInjection/Iter8LdapRecordExtensionTest.php
+++ b/Tests/DependencyInjection/Iter8LdapRecordExtensionTest.php
@@ -29,30 +29,39 @@ public function test_load_empty_configuration(): void
{
$this->expectException(InvalidConfigurationException::class);
- $container = $this->createContainer();
- $container->registerExtension(new Iter8LdapRecordExtension());
- $container->loadFromExtension('iter8_ldap_record');
- $container->compile();
+ $this->createContainerWithConfig([]);
}
public function test_load_valid_configuration(): void
{
- $container = $this->createContainer();
- $container->registerExtension(new Iter8LdapRecordExtension());
- $container->loadFromExtension('iter8_ldap_record', $this->baseConfig());
- $container->compile();
+ $ldapConfig = $this->getLdapConfig();
+
+ $config = \array_merge(
+ $this->baseConfig(),
+ [
+ 'hosts' => [$ldapConfig['host']],
+ 'port' => $ldapConfig['port'],
+ ]
+ );
+
+ $container = $this->createContainerWithConfig($config);
self::assertTrue($container->getDefinition('iter8_ldap_record.connection')->isPublic());
}
public function test_is_connected_with_auto_connect_disabled(): void
{
- $this->getLdapConfig();
+ $ldapConfig = $this->getLdapConfig();
- $container = $this->createContainer();
- $container->registerExtension(new Iter8LdapRecordExtension());
- $container->loadFromExtension('iter8_ldap_record', $this->baseConfig());
- $container->compile();
+ $config = \array_merge(
+ $this->baseConfig(),
+ [
+ 'hosts' => [$ldapConfig['host']],
+ 'port' => $ldapConfig['port'],
+ ]
+ );
+
+ $container = $this->createContainerWithConfig($config);
/** @var Connection $connection */
$connection = $container->get('iter8_ldap_record.connection');
@@ -62,35 +71,112 @@ public function test_is_connected_with_auto_connect_disabled(): void
public function test_is_connected_with_auto_connect_enabled(): void
{
- $this->getLdapConfig();
+ $ldapConfig = $this->getLdapConfig();
- $config = array_merge(
+ $config = \array_merge(
$this->baseConfig(),
- ['auto_connect' => true]
+ [
+ 'hosts' => [$ldapConfig['host']],
+ 'port' => $ldapConfig['port'],
+ 'auto_connect' => true,
+ ]
);
- $container = $this->createContainer();
- $container->registerExtension(new Iter8LdapRecordExtension());
- $container->loadFromExtension('iter8_ldap_record', $config);
- $container->compile();
+ $container = $this->createContainerWithConfig($config);
+
+ /** @var Connection $connection */
+ $connection = $container->get('iter8_ldap_record.connection');
+
+ self::assertTrue($connection->isConnected());
+ }
+
+ public function test_manual_connect_with_unsecured_connection(): void
+ {
+ $ldapConfig = $this->getLdapConfig();
+
+ $config = \array_merge(
+ $this->baseConfig(),
+ [
+ 'hosts' => [$ldapConfig['host']],
+ 'port' => $ldapConfig['port'],
+ ]
+ );
+
+ $container = $this->createContainerWithConfig($config);
+
+ /** @var Connection $connection */
+ $connection = $container->get('iter8_ldap_record.connection');
+
+ $connection->connect();
+
+ self::assertTrue($connection->isConnected());
+ }
+
+ public function test_manual_connect_with_tls_connection(): void
+ {
+ $ldapConfig = $this->getLdapsConfig();
+
+ $config = \array_merge(
+ $this->baseConfig(),
+ [
+ 'hosts' => [$ldapConfig['host']],
+ 'port' => $ldapConfig['port'],
+ 'use_tls' => true,
+ ]
+ );
+
+ $container = $this->createContainerWithConfig($config);
/** @var Connection $connection */
$connection = $container->get('iter8_ldap_record.connection');
+ $connection->connect();
+
self::assertTrue($connection->isConnected());
}
+ public function test_can_find_user(): void
+ {
+ $ldapConfig = $this->getLdapConfig();
+
+ $config = \array_merge(
+ $this->baseConfig(),
+ [
+ 'hosts' => [$ldapConfig['host']],
+ 'port' => $ldapConfig['port'],
+ ]
+ );
+
+ $container = $this->createContainerWithConfig($config);
+
+ /** @var Connection $connection */
+ $connection = $container->get('iter8_ldap_record.connection');
+
+ $results = $connection->query()->where('cn', '=', 'a')->get();
+
+ dump($results);
+ self::assertNotEmpty($results);
+ }
+
private function baseConfig(): array
{
return [
- 'hosts' => ['localhost'],
'base_dn' => 'dc=local,dc=com',
'username' => 'cn=admin,dc=local,dc=com',
'password' => 'a_great_password',
- 'port' => 3389,
];
}
+ private function createContainerWithConfig(array $config): ContainerBuilder
+ {
+ $container = $this->createContainer();
+ $container->registerExtension(new Iter8LdapRecordExtension());
+ $container->loadFromExtension('iter8_ldap_record', $config);
+ $container->compile();
+
+ return $container;
+ }
+
private function createContainer(): ContainerBuilder
{
return new ContainerBuilder(new ParameterBag([
diff --git a/Tests/TestCase.php b/Tests/TestCase.php
index c667bc0..e3c17fb 100644
--- a/Tests/TestCase.php
+++ b/Tests/TestCase.php
@@ -6,6 +6,9 @@
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
+/**
+ * @see https://github.com/symfony/symfony/blob/89fedfa/src/Symfony/Component/Ldap/Tests/LdapTestCase.php
+ */
class TestCase extends PHPUnitTestCase
{
protected function getLdapConfig(): array
@@ -22,7 +25,40 @@ protected function getLdapConfig(): array
return [
'host' => getenv('LDAP_HOST'),
- 'port' => getenv('LDAP_PORT'),
+ 'port' => (int) getenv('LDAP_PORT'),
+ ];
+ }
+
+ protected function getLdapsConfig(): array
+ {
+ putenv('TLS_REQCERT=allow');
+
+// @ldap_set_option(null, \LDAP_OPT_DEBUG_LEVEL, 7);
+ @ldap_set_option(null, \LDAP_OPT_X_TLS_REQUIRE_CERT, \LDAP_OPT_X_TLS_ALLOW);
+ /** @var resource|null $h */
+ $h = @ldap_connect((string) getenv('LDAP_HOST'), (int) getenv('LDAPS_PORT'));
+ @ldap_set_option($h, \LDAP_OPT_PROTOCOL_VERSION, 3);
+ @ldap_set_option($h, \LDAP_OPT_REFERRALS, 0);
+ if (\is_resource($h)) {
+ @ldap_get_option($h, \LDAP_OPT_DIAGNOSTIC_MESSAGE, $extendedError);
+ @ldap_start_tls($h);
+ }
+
+ if (!\is_resource($h) || !@ldap_bind($h)) {
+// dump(@ldap_error($h));
+// dump($extendedError);
+ self::markTestSkipped(\sprintf(
+ 'No server is listening on LDAP_HOST:LDAPS_PORT (%s:%s)',
+ getenv('LDAP_HOST'),
+ getenv('LDAPS_PORT')
+ ));
+ }
+
+ ldap_unbind($h);
+
+ return [
+ 'host' => getenv('LDAP_HOST'),
+ 'port' => (int) getenv('LDAPS_PORT'),
];
}
}
diff --git a/Tests/certs/generate.sh b/Tests/certs/generate.sh
new file mode 100755
index 0000000..5d599c6
--- /dev/null
+++ b/Tests/certs/generate.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -e
+
+SCRIPT_PATH=$(dirname "$(realpath "$0")")
+
+# Create a root CA signing key.
+openssl genrsa -out "${SCRIPT_PATH}/openldapCA.key" 4096
+
+# Now create and self-sign the root CA certificate.
+openssl req -x509 -new -nodes -key "${SCRIPT_PATH}/openldapCA.key" -sha256 -days 3650 -subj "/CN=localhostCA" -out "${SCRIPT_PATH}/openldapCA.crt"
+
+# Generate the LDAP server key.
+openssl genrsa -out "${SCRIPT_PATH}/openldap.key" 2048
+
+# Now create the CSR for the LDAP server certificate so we can sign it with our root CA.
+openssl req -new -sha256 -key "${SCRIPT_PATH}/openldap.key" -subj "/CN=localhost" -out "${SCRIPT_PATH}/openldap.csr"
+
+# Finally, sign the LDAP server CSR with our root CA so it's ready to use.
+openssl x509 -req -in "${SCRIPT_PATH}/openldap.csr" -CA "${SCRIPT_PATH}/openldapCA.crt" -CAkey "${SCRIPT_PATH}/openldapCA.key" -CAcreateserial -out "${SCRIPT_PATH}/openldap.crt" -sha256 -days 3650
+
+# Remove the CSR as it's no longer needed.
+rm "${SCRIPT_PATH}/openldap.csr"
+
+exit 0
diff --git a/composer.json b/composer.json
index 9d3f13b..f445d18 100644
--- a/composer.json
+++ b/composer.json
@@ -65,6 +65,7 @@
"phpstan": "phpstan analyze",
"phpstan-max": "@phpstan --level=max",
"phpunit": "phpunit",
- "psalm": "psalm --show-info=true"
+ "psalm": "psalm --show-info=true",
+ "generate-certs": "./Tests/certs/generate.sh"
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 568296a..ccc1e04 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,14 +1,24 @@
-version: '2'
+version: '3.8'
services:
ldap:
- image: bitnami/openldap
+ image: bitnami/openldap:latest
+ network_mode: "bridge"
ports:
- - 3389:3389
+ - 1389:1389
+ - 1636:1636
environment:
- LDAP_ADMIN_USERNAME=admin
- LDAP_ADMIN_PASSWORD=a_great_password
- LDAP_USERS=a
- LDAP_PASSWORDS=a
- LDAP_ROOT=dc=local,dc=com
- - LDAP_PORT_NUMBER=3389
+ - LDAP_PORT_NUMBER=1389
+ - LDAP_ENABLE_TLS=yes
+ - LDAP_LDAPS_PORT_NUMBER=1636
+ - LDAP_TLS_VERIFY_CLIENT=try
+ - LDAP_TLS_CERT_FILE=/opt/bitnami/openldap/certs/openldap.crt
+ - LDAP_TLS_KEY_FILE=/opt/bitnami/openldap/certs/openldap.key
+ - LDAP_TLS_CA_FILE=/opt/bitnami/openldap/certs/openldapCA.crt
+ volumes:
+ - ./Tests/certs:/opt/bitnami/openldap/certs
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index ec27d43..49c2517 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -48,6 +48,10 @@
Tests
+
+ Tests
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index b3988ba..fed6ae8 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -11,7 +11,8 @@
-
+
+