Introducing the LDAP component

This commit is contained in:
Charles Sarrazin 2015-09-25 11:29:08 +02:00
parent dc937f899e
commit 1c964b993f
12 changed files with 476 additions and 0 deletions

View File

@ -35,6 +35,7 @@ before_install:
- if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi;
- if [[ "$TRAVIS_PHP_VERSION" = 5.* ]] && [ "$deps" = "no" ]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi;
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi;
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "extension = ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
- ./phpunit install
- export PHPUNIT="$(readlink -f ./phpunit)"

View File

@ -45,6 +45,7 @@ install:
- IF %PHP_EXT%==1 echo extension=php_mbstring.dll >> php.ini
- IF %PHP_EXT%==1 echo extension=php_fileinfo.dll >> php.ini
- IF %PHP_EXT%==1 echo extension=php_pdo_sqlite.dll >> php.ini
- IF %PHP_EXT%==1 echo extension=php_ldap.dll >> php.ini
- cd c:\projects\symfony
- php phpunit install
- IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev)

3
src/Symfony/Component/Ldap/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Ldap\Exception;
/**
* ConnectionException is throw if binding to ldap can not be established.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ConnectionException extends \RuntimeException
{
}

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Ldap\Exception;
/**
* LdapException is throw if php ldap module is not loaded.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class LdapException extends \RuntimeException
{
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2015 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,220 @@
<?php
namespace Symfony\Component\Ldap;
use Symfony\Component\Ldap\Exception\ConnectionException;
use Symfony\Component\Ldap\Exception\LdapException;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Francis Besset <francis.besset@gmail.com>
* @author Charles Sarrazin <charles@sarraz.in>
*/
class LdapClient implements LdapClientInterface
{
private $host;
private $port;
private $version;
private $useSsl;
private $useStartTls;
private $optReferrals;
private $connection;
private $charmaps;
/**
* Constructor.
*
* @param string $host
* @param int $port
* @param int $version
* @param bool $useSsl
* @param bool $useStartTls
* @param bool $optReferrals
*/
public function __construct($host = null, $port = 389, $version = 3, $useSsl = false, $useStartTls = false, $optReferrals = false)
{
if (!extension_loaded('ldap')) {
throw new LdapException('The ldap module is needed.');
}
$this->host = $host;
$this->port = $port;
$this->version = $version;
$this->useSsl = (bool) $useSsl;
$this->useStartTls = (bool) $useStartTls;
$this->optReferrals = (bool) $optReferrals;
}
public function __destruct()
{
$this->disconnect();
}
/**
* {@inheritdoc}
*/
public function bind($dn = null, $password = null)
{
if (!$this->connection) {
$this->connect();
}
if (false === @ldap_bind($this->connection, $dn, $password)) {
throw new ConnectionException(ldap_error($this->connection));
}
}
/**
* {@inheritdoc}
*/
public function find($dn, $query, $filter = '*')
{
if (!is_array($filter)) {
$filter = array($filter);
}
$search = ldap_search($this->connection, $dn, $query, $filter);
$infos = ldap_get_entries($this->connection, $search);
if (0 === $infos['count']) {
return;
}
return $infos;
}
/**
* {@inheritdoc}
*/
public function escape($subject, $ignore = '', $flags = 0)
{
if (function_exists('ldap_escape')) {
return ldap_escape($subject, $ignore, $flags);
}
return $this->doEscape($subject, $ignore, $flags);
}
private function connect()
{
if (!$this->connection) {
$host = $this->host;
if ($this->useSsl) {
$host = 'ldaps://'.$host;
}
ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $this->version);
ldap_set_option($this->connection, LDAP_OPT_REFERRALS, $this->optReferrals);
$this->connection = ldap_connect($host, $this->port);
if ($this->useStartTls) {
ldap_start_tls($this->connection);
}
}
}
private function disconnect()
{
if ($this->connection && is_resource($this->connection)) {
ldap_unbind($this->connection);
}
$this->connection = null;
}
/**
* Stub implementation of the {@link ldap_escape()} function of the ldap
* extension.
*
* Escape strings for safe use in LDAP filters and DNs.
*
* @author Chris Wright <ldapi@daverandom.com>
*
* @param string $subject
* @param string $ignore
* @param int $flags
*
* @return string
*
* @see http://stackoverflow.com/a/8561604
*/
private function doEscape($subject, $ignore = '', $flags = 0)
{
$charMaps = $this->getCharmaps();
// Create the base char map to escape
$flags = (int) $flags;
$charMap = array();
if ($flags & self::LDAP_ESCAPE_FILTER) {
$charMap += $charMaps[self::LDAP_ESCAPE_FILTER];
}
if ($flags & self::LDAP_ESCAPE_DN) {
$charMap += $charMaps[self::LDAP_ESCAPE_DN];
}
if (!$charMap) {
$charMap = $charMaps[0];
}
// Remove any chars to ignore from the list
$ignore = (string) $ignore;
for ($i = 0, $l = strlen($ignore); $i < $l; ++$i) {
unset($charMap[$ignore[$i]]);
}
// Do the main replacement
$result = strtr($subject, $charMap);
// Encode leading/trailing spaces if LDAP_ESCAPE_DN is passed
if ($flags & self::LDAP_ESCAPE_DN) {
if ($result[0] === ' ') {
$result = '\\20'.substr($result, 1);
}
if ($result[strlen($result) - 1] === ' ') {
$result = substr($result, 0, -1).'\\20';
}
}
return $result;
}
private function getCharmaps()
{
if (null !== $this->charmaps) {
return $this->charmaps;
}
$charMaps = array(
self::LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"),
self::LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#'),
);
$charMaps[0] = array();
for ($i = 0; $i < 256; ++$i) {
$charMaps[0][chr($i)] = sprintf('\\%02x', $i);
}
for ($i = 0, $l = count($charMaps[self::LDAP_ESCAPE_FILTER]); $i < $l; ++$i) {
$chr = $charMaps[self::LDAP_ESCAPE_FILTER][$i];
unset($charMaps[self::LDAP_ESCAPE_FILTER][$i]);
$charMaps[self::LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr];
}
for ($i = 0, $l = count($charMaps[self::LDAP_ESCAPE_DN]); $i < $l; ++$i) {
$chr = $charMaps[self::LDAP_ESCAPE_DN][$i];
unset($charMaps[self::LDAP_ESCAPE_DN][$i]);
$charMaps[self::LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr];
}
$this->charmaps = $charMaps;
return $this->charmaps;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Symfony\Component\Ldap;
use Symfony\Component\Ldap\Exception\ConnectionException;
/**
* Ldap interface.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Charles Sarrazin <charles@sarraz.in>
*/
interface LdapClientInterface
{
const LDAP_ESCAPE_FILTER = 0x01;
const LDAP_ESCAPE_DN = 0x02;
/**
* Return a connection bound to the ldap.
*
* @param string $dn A LDAP dn
* @param string $password A password
*
* @throws ConnectionException If dn / password could not be bound.
*/
public function bind($dn = null, $password = null);
/*
* Find a username into ldap connection.
*
* @param string $dn
* @param string $query
* @param mixed $filter
*
* @return array|null
*/
public function find($dn, $query, $filter = '*');
/**
* Escape a string for use in an LDAP filter or DN.
*
* @param string $subject
* @param string $ignore
* @param int $flags
*
* @return string
*/
public function escape($subject, $ignore = '', $flags = 0);
}

View File

@ -0,0 +1,23 @@
Ldap Component
=============
A Ldap client for PHP on top of PHP's ldap extension.
This component also provides a stub for the missing
`ldap_escape` function in PHP versions lower than 5.6.
Documentation
-------------
The documentation for the component can be found [online] [0].
Resources
---------
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/Ldap/
$ composer install
$ phpunit
[0]: https://symfony.com/doc/2.8/components/ldap.html

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Tests\Authentication\Provider;
use Symfony\Component\Ldap\LdapClient;
class LdapClientTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!extension_loaded('ldap')) {
$this->markTestSkipped('The ldap extension is not available');
}
}
/**
* @dataProvider provideLdapEscapeValues
*/
public function testLdapEscape($subject, $ignore, $flags, $expected)
{
$ldap = new LdapClient();
$this->assertSame($expected, $ldap->escape($subject, $ignore, $flags));
}
/**
* Provides values for the ldap_escape shim. These tests come from the official
* extension.
*
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_dn.phpt
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_all.phpt
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_both.phpt
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_filter.phpt
* @see https://github.com/php/php-src/blob/master/ext/ldap/tests/ldap_escape_ignore.phpt
*
* @return array
*/
public function provideLdapEscapeValues()
{
return array(
array('foo=bar(baz)*', null, LdapClient::LDAP_ESCAPE_DN, 'foo\3dbar(baz)*'),
array('foo=bar(baz)*', null, null, '\66\6f\6f\3d\62\61\72\28\62\61\7a\29\2a'),
array('foo=bar(baz)*', null, LdapClient::LDAP_ESCAPE_DN | LdapClient::LDAP_ESCAPE_FILTER, 'foo\3dbar\28baz\29\2a'),
array('foo=bar(baz)*', null, LdapClient::LDAP_ESCAPE_FILTER, 'foo=bar\28baz\29\2a'),
array('foo=bar(baz)*', 'ao', null, '\66oo\3d\62a\72\28\62a\7a\29\2a'),
);
}
}

View File

@ -0,0 +1,34 @@
{
"name": "symfony/ldap",
"type": "library",
"description": "An abstraction in front of PHP's LDAP functions, compatible with PHP 5.3.9 onwards.",
"keywords": ["ldap", "active directory"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Charles Sarrazin",
"email": "charles@sarraz.in"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.9",
"ext-ldap": "*"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7|~3.0.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Ldap\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
}
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony Ldap Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>