Initial commit
This commit is contained in:
commit
d4a8821157
10 changed files with 1669 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/composer.lock
|
||||||
|
/vendor
|
||||||
|
/.phpunit.cache
|
||||||
|
/coverage*
|
13
.pre-commit-config.yaml
Normal file
13
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Pre-commit hooks to run tests and ensure code is cleaned.
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/digitalpulp/pre-commit-php.git
|
||||||
|
rev: 1.4.0
|
||||||
|
hooks:
|
||||||
|
- id: php-stan
|
||||||
|
files: \.(php)$
|
||||||
|
args: ['--configuration=phpstan.neon', '--xdebug']
|
||||||
|
- repo: https://github.com/digitalpulp/pre-commit-php.git
|
||||||
|
rev: 1.4.0
|
||||||
|
hooks:
|
||||||
|
- id: php-unit
|
21
composer.json
Normal file
21
composer.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "brenard/eesyldap",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Object oriented interface for searching and manipulating LDAP entries & filters",
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.10",
|
||||||
|
"phpunit/phpunit": "^9"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Benjamin Renard",
|
||||||
|
"email": "brenard@zionetrix.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"EesyLDAP\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
phpstan.neon
Normal file
10
phpstan.neon
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
parameters:
|
||||||
|
level: 9
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
- tests
|
||||||
|
treatPhpDocTypesAsCertain: false
|
||||||
|
ignoreErrors:
|
||||||
|
-
|
||||||
|
message: "#Method .*::test.*\\(\\) has no return type specified\\.#"
|
||||||
|
path: tests/*
|
27
phpunit.xml
Normal file
27
phpunit.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
cacheResultFile=".phpunit.cache/test-results"
|
||||||
|
executionOrder="depends,defects"
|
||||||
|
forceCoversAnnotation="true"
|
||||||
|
beStrictAboutCoversAnnotation="true"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
beStrictAboutTodoAnnotatedTests="true"
|
||||||
|
convertDeprecationsToExceptions="true"
|
||||||
|
failOnRisky="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
verbose="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="default">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<coverage cacheDirectory=".phpunit.cache/code-coverage"
|
||||||
|
processUncoveredFiles="true">
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">src</directory>
|
||||||
|
</include>
|
||||||
|
</coverage>
|
||||||
|
</phpunit>
|
645
src/Filter.php
Normal file
645
src/Filter.php
Normal file
|
@ -0,0 +1,645 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyLDAP;
|
||||||
|
|
||||||
|
use EesyLDAP\Filter\CombineException;
|
||||||
|
use EesyLDAP\Filter\FilterException;
|
||||||
|
use EesyLDAP\Filter\ParserException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LDAP Filter String abstraction class
|
||||||
|
*
|
||||||
|
* Note: originally based on Net_LDAP2_Filter implementation.
|
||||||
|
* @see https://github.com/pear/Net_LDAP2
|
||||||
|
*
|
||||||
|
* @property-read string|null $attr_name
|
||||||
|
* @property-read string|null $attribute
|
||||||
|
* @property-read string|null $op
|
||||||
|
* @property-read string|null $operator
|
||||||
|
* @property-read string|null $pattern
|
||||||
|
* @property-read string|null $log_op
|
||||||
|
* @property-read string|null $logical_operator
|
||||||
|
* @property-read array<Filter> $sub_filters
|
||||||
|
*/
|
||||||
|
class Filter {
|
||||||
|
|
||||||
|
/*
|
||||||
|
*************************************************************************************************
|
||||||
|
* Leaf mode
|
||||||
|
*************************************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attribute name
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $attr_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The operator
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attribute value pattern
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $pattern;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*************************************************************************************************
|
||||||
|
* Combine mode
|
||||||
|
*************************************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logical operator
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $log_op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LDAP sub-filters objects
|
||||||
|
* @var array<Filter>|null
|
||||||
|
*/
|
||||||
|
private $sub_filters;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*************************************************************************************************
|
||||||
|
* Some constants
|
||||||
|
*************************************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of possible logical operators
|
||||||
|
* @var array<string>
|
||||||
|
*/
|
||||||
|
private static $log_ops = array('&', '|', '!');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of logical operators aliases
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
private static $log_op_aliases = array('and' => '&', 'or' => '|', 'not' => '!');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NOT logical operator
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $not_op = '!';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of possible operators
|
||||||
|
* @var array<string>
|
||||||
|
*/
|
||||||
|
private static $ops = array('=', '<', '>', '<=', '>=', '~=');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of operators aliases
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
private static $op_aliases = array(
|
||||||
|
'equals' => '=',
|
||||||
|
'==' => '=',
|
||||||
|
'lower' => '<',
|
||||||
|
'greater' => '>',
|
||||||
|
'lower_or_equals' => '<=',
|
||||||
|
'greater_or_equals' => '>=',
|
||||||
|
'approx' => '~=',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Characters to escape in a LDAP filter string
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
private static $espace_chars = array (
|
||||||
|
'\\' => '\5c', // \
|
||||||
|
'(' => '\28', // (
|
||||||
|
')' => '\29', // )
|
||||||
|
'*' => '\2a', // *
|
||||||
|
"\u{0000}" => '\00', // NUL
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to match on OID
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $oid_regex = '/^[0-9]+(\.[0-9]+)+$/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to match attribute name
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $attr_name_regex = '/^(?P<name>[a-zA-Z][a-zA-Z0-9\-]*)(;(?P<option>[a-zA-Z0-9\-]+))*$/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to match extensible attribute name
|
||||||
|
* @throws FilterException
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $ext_attr_name_regex = '/^(?P<name>[a-zA-Z0-9\-]+)?\:((?P<dn>dn|DN)\:)?((?P<matching_rule>[a-zA-Z]+|[0-9]+(\.[0-9]+)*)\:)?$/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param array<string|Filter|array<string|Filter>|bool> $args
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(...$args) {
|
||||||
|
// Handle espace optional argument
|
||||||
|
$escape = true;
|
||||||
|
if ($args && is_bool(end($args)))
|
||||||
|
$escape = array_pop($args);
|
||||||
|
|
||||||
|
if (!$args)
|
||||||
|
throw new FilterException('Invalid constructor arguments: no argument provided!');
|
||||||
|
|
||||||
|
// One logical operator followed by some filters
|
||||||
|
if (self :: is_log_op($args[0])) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$this -> log_op = self :: unalias_log_op(array_shift($args));
|
||||||
|
|
||||||
|
// Convert args as filters
|
||||||
|
$this -> sub_filters = array();
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
if (!is_array($arg))
|
||||||
|
$arg = array($arg);
|
||||||
|
foreach ($arg as $a) {
|
||||||
|
if (is_string($a))
|
||||||
|
$a = self :: parse($a);
|
||||||
|
if (!$a instanceof Filter)
|
||||||
|
throw new FilterException(
|
||||||
|
'Invalid constructor arguments: logical operator must be followed by Filter object, '.
|
||||||
|
'filter string or array of Filter or filter string.'
|
||||||
|
);
|
||||||
|
$this -> sub_filters[] = $a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check number of filters against logical operator
|
||||||
|
if (self :: is_not_op($this -> log_op) && count($this -> sub_filters) != 1)
|
||||||
|
throw new FilterException(
|
||||||
|
'Invalid constructor arguments: NOT operator must be followed by exactly one filter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// array('attribute', 'operator', 'pattern')
|
||||||
|
if (count($args) == 3) {
|
||||||
|
if (
|
||||||
|
self :: is_attr_name($args[0])
|
||||||
|
&& (self :: is_op($args[1]) || in_array($args[1], array('begins', 'contains', 'ends')))
|
||||||
|
&& is_string($args[2])
|
||||||
|
) {
|
||||||
|
$this -> attr_name = $args[0];
|
||||||
|
$this -> pattern = $escape?self :: escape($args[2]):$args[2];
|
||||||
|
switch ($args[1]) {
|
||||||
|
case 'begins':
|
||||||
|
$this -> op = '=';
|
||||||
|
$this -> pattern = sprintf('%s*', $this -> pattern);
|
||||||
|
break;
|
||||||
|
case 'contains':
|
||||||
|
$this -> op = '=';
|
||||||
|
$this -> pattern = sprintf('*%s*', $this -> pattern);
|
||||||
|
break;
|
||||||
|
case 'ends':
|
||||||
|
$this -> op = '=';
|
||||||
|
$this -> pattern = sprintf('*%s', $this -> pattern);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$this -> op = self :: unalias_op($args[1]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// array('attribute', 'present|any')
|
||||||
|
if (count($args) == 2) {
|
||||||
|
if (
|
||||||
|
self :: is_attr_name($args[0])
|
||||||
|
&& in_array($args[1], array('present', 'any'))
|
||||||
|
) {
|
||||||
|
$this -> attr_name = $args[0];
|
||||||
|
$this -> op = '=';
|
||||||
|
$this -> pattern = '*';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FilterException(
|
||||||
|
'Invalid constructor arguments provided to construct a Filter object');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get filter property
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get($key) {
|
||||||
|
switch ($key) {
|
||||||
|
case 'attr_name':
|
||||||
|
case 'attribute':
|
||||||
|
return isset($this -> attr_name)?$this -> attr_name:null;
|
||||||
|
|
||||||
|
case 'op':
|
||||||
|
case 'operator':
|
||||||
|
return isset($this -> op)?$this -> op:null;
|
||||||
|
|
||||||
|
case 'pattern':
|
||||||
|
return isset($this -> pattern)?$this -> pattern:null;
|
||||||
|
|
||||||
|
case 'log_op':
|
||||||
|
case 'logical_operator':
|
||||||
|
return isset($this -> log_op)?$this -> log_op:null;
|
||||||
|
|
||||||
|
case 'sub_filters':
|
||||||
|
return isset($this -> sub_filters)?$this -> sub_filters:array();
|
||||||
|
}
|
||||||
|
throw new FilterException("Invalid property '$key' requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get operators
|
||||||
|
* @return array<string>
|
||||||
|
*/
|
||||||
|
public static function operators() {
|
||||||
|
return self :: $ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get operator aliases
|
||||||
|
* @return array<string,string>
|
||||||
|
*/
|
||||||
|
public static function operator_aliases() {
|
||||||
|
return self :: $op_aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get logical operators
|
||||||
|
* @return array<string>
|
||||||
|
*/
|
||||||
|
public static function logical_operators() {
|
||||||
|
return self :: $log_ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get logical operator aliases
|
||||||
|
* @return array<string,string>
|
||||||
|
*/
|
||||||
|
public static function logical_operator_aliases() {
|
||||||
|
return self :: $log_op_aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check filter is a leaf one
|
||||||
|
* @phpstan-assert-if-true string $this->attr_name
|
||||||
|
* @phpstan-assert-if-true string $this->op
|
||||||
|
* @phpstan-assert-if-true string $this->pattern
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_leaf() {
|
||||||
|
return isset($this -> attr_name) && isset($this -> op) && isset($this -> pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check filter is a combine one
|
||||||
|
* @phpstan-assert-if-true string $this->log_op
|
||||||
|
* @phpstan-assert-if-true non-empty-array<Filter> $this->sub_filters
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_combine() {
|
||||||
|
return (
|
||||||
|
isset($this -> log_op) && isset($this -> sub_filters) && is_array($this -> sub_filters)
|
||||||
|
&& $this -> sub_filters
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return LDAP filter as a string
|
||||||
|
* @param bool $pretty Enable pretty format: as line return and tabulations to easily distinguate
|
||||||
|
* logical groups.
|
||||||
|
* @param int $level Level of iteration (internally use in pretty format mode)
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function as_string($pretty=false, $level=0) {
|
||||||
|
// Leaf filter
|
||||||
|
if ($this -> is_leaf()) {
|
||||||
|
if ($pretty)
|
||||||
|
return (
|
||||||
|
str_repeat("\t", $level).
|
||||||
|
sprintf("(%s%s%s)", $this -> attr_name, $this -> op, $this -> pattern).
|
||||||
|
($level?"\n":"")
|
||||||
|
);
|
||||||
|
return sprintf("(%s%s%s)", $this -> attr_name, $this -> op, $this -> pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine filter
|
||||||
|
if ($this -> is_combine()) {
|
||||||
|
$return = '';
|
||||||
|
foreach ($this->sub_filters as $filter)
|
||||||
|
$return .= $filter -> as_string($pretty, $level+1);
|
||||||
|
if ($pretty)
|
||||||
|
return (
|
||||||
|
str_repeat("\t", $level).
|
||||||
|
"(".$this -> log_op."\n".
|
||||||
|
$return.
|
||||||
|
str_repeat("\t", $level).")".
|
||||||
|
($level?"\n":"")
|
||||||
|
);
|
||||||
|
return sprintf("(%s%s)", $this -> log_op, $return);
|
||||||
|
}
|
||||||
|
|
||||||
|
// May never occured
|
||||||
|
throw new FilterException('Filter is not leaf nor combine one?!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine LDAP filter objects or strings using a logical operator
|
||||||
|
*
|
||||||
|
* @param string $log_op The locical operator. May be "and", "or", "not" or the subsequent logical
|
||||||
|
* equivalents "&", "|", "!"
|
||||||
|
* @param array<string|Filter|array<string|Filter>> $args LDAP filters to combine: could be
|
||||||
|
* LDAP objects, LDAP strings or array of
|
||||||
|
* LDAP objects or LDAP strings.
|
||||||
|
* @throws CombineException
|
||||||
|
* @return Filter
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function combine($log_op, ...$args) {
|
||||||
|
// Unalias & check logical operator
|
||||||
|
$log_op = self :: unalias_log_op($log_op);
|
||||||
|
if (!$log_op) throw new CombineException('Invalid logical operator provided!');
|
||||||
|
|
||||||
|
// Convert args as filters
|
||||||
|
$filters = array();
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
if (!is_array($arg))
|
||||||
|
$arg = array($arg);
|
||||||
|
foreach ($arg as $a) {
|
||||||
|
if (is_string($a))
|
||||||
|
$a = self :: parse($a);
|
||||||
|
if (!$a instanceof Filter)
|
||||||
|
throw new CombineException(
|
||||||
|
'Invalid filter provided: must be a Filter object or a LDAP filter string!'
|
||||||
|
);
|
||||||
|
$filters[] = $a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check number of filters against logical operator
|
||||||
|
if (self :: is_not_op($log_op)) {
|
||||||
|
if (count($filters) != 1)
|
||||||
|
throw new CombineException('NOT operator accept only one filter!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Filter($log_op, ...$filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an LDAP filter string
|
||||||
|
* @param string $value The LDAP filter string to parse
|
||||||
|
* @throws ParserException
|
||||||
|
* @static
|
||||||
|
* @return Filter
|
||||||
|
*/
|
||||||
|
public static function parse($value) {
|
||||||
|
// Only handle filter enclosed with brackets
|
||||||
|
if (!preg_match('/^\((.+?)\)$/', $value, $matches))
|
||||||
|
return self :: parse("($value)");
|
||||||
|
|
||||||
|
// Check for right bracket syntax: count of unescaped opening
|
||||||
|
// brackets must match count of unescaped closing brackets.
|
||||||
|
// At this stage we may have:
|
||||||
|
// 1. one filter component with already removed outer brackets
|
||||||
|
// 2. one or more subfilter components
|
||||||
|
$c_openbracks = preg_match_all('/(?<!\\\\)\(/' , $matches[1], $notrelevant);
|
||||||
|
$c_closebracks = preg_match_all('/(?<!\\\\)\)/' , $matches[1], $notrelevant);
|
||||||
|
if ($c_openbracks != $c_closebracks) {
|
||||||
|
throw new ParserException(
|
||||||
|
"Filter parsing error: invalid filter syntax - opening brackets do not match close ".
|
||||||
|
"brackets!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
|
||||||
|
// Subfilter processing: pass subfilters to parse() and combine
|
||||||
|
// the objects using the logical operator detected
|
||||||
|
// we have now something like "&(...)(...)(...)" but at least one part ("!(...)").
|
||||||
|
// Each subfilter could be an arbitary complex subfilter.
|
||||||
|
|
||||||
|
// extract logical operator and filter arguments
|
||||||
|
$log_op = substr($matches[1], 0, 1);
|
||||||
|
$remaining_component = substr($matches[1], 1);
|
||||||
|
|
||||||
|
// split $remaining_component into individual subfilters
|
||||||
|
// we cannot use split() for this, because we do not know the
|
||||||
|
// complexiness of the subfilter. Thus, we look trough the filter
|
||||||
|
// string and just recognize ending filters at the first level.
|
||||||
|
// We record the index number of the char and use that information
|
||||||
|
// later to split the string.
|
||||||
|
$sub_index_pos = array();
|
||||||
|
$prev_char = ''; // previous character looked at
|
||||||
|
$level = 0; // denotes the current bracket level we are,
|
||||||
|
// >1 is too deep, 1 is ok, 0 is outside any
|
||||||
|
// subcomponent
|
||||||
|
for ($curpos = 0; $curpos < strlen($remaining_component); $curpos++) {
|
||||||
|
$cur_char = substr($remaining_component, $curpos, 1);
|
||||||
|
|
||||||
|
// rise/lower bracket level
|
||||||
|
if ($cur_char == '(' && $prev_char != '\\') {
|
||||||
|
$level++;
|
||||||
|
} elseif ($cur_char == ')' && $prev_char != '\\') {
|
||||||
|
$level--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cur_char == '(' && $prev_char == ')' && $level == 1) {
|
||||||
|
array_push($sub_index_pos, $curpos); // mark the position for splitting
|
||||||
|
}
|
||||||
|
$prev_char = $cur_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now perform the splits. To get also the last part, we
|
||||||
|
// need to add the "END" index to the split array
|
||||||
|
array_push($sub_index_pos, strlen($remaining_component));
|
||||||
|
$subfilters = array();
|
||||||
|
$oldpos = 0;
|
||||||
|
foreach ($sub_index_pos as $s_pos) {
|
||||||
|
$str_part = substr($remaining_component, $oldpos, $s_pos - $oldpos);
|
||||||
|
array_push($subfilters, $str_part);
|
||||||
|
$oldpos = $s_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// some error checking...
|
||||||
|
if (count($subfilters) > 1) {
|
||||||
|
// several subfilters found
|
||||||
|
if ($log_op == "!") {
|
||||||
|
throw new ParserException(
|
||||||
|
"Filter parsing error: invalid filter syntax - NOT operator detected but ".
|
||||||
|
"several arguments given!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now parse the subfilters into objects and combine them using the operator
|
||||||
|
$subfilters_o = array();
|
||||||
|
foreach ($subfilters as $subfilter) {
|
||||||
|
array_push($subfilters_o, self :: parse($subfilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
return self :: combine($log_op, $subfilters_o);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is one leaf filter component, do some syntax checks, then escape and build filter_o
|
||||||
|
// $matches[1] should be now something like "foo=bar"
|
||||||
|
|
||||||
|
// detect multiple leaf components
|
||||||
|
// [TODO] Maybe this will make problems with filters containing brackets inside the value
|
||||||
|
if (stristr($matches[1], ')') || stristr($matches[1], '(')) {
|
||||||
|
throw new ParserException(
|
||||||
|
"Filter parsing error: invalid filter syntax - multiple leaf components ".
|
||||||
|
"detected!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$filter_parts = self :: split_attr_string($matches[1]);
|
||||||
|
if (!is_array($filter_parts) || count($filter_parts) != 3) {
|
||||||
|
throw new ParserException(
|
||||||
|
"Filter parsing error: invalid filter syntax - unknown matching rule used");
|
||||||
|
}
|
||||||
|
$filter_parts[] = false; // Disable escaping
|
||||||
|
return new Filter(...$filter_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits an attribute=value syntax into an array
|
||||||
|
* @param string $value
|
||||||
|
* @static
|
||||||
|
* @return array<int,string>|false
|
||||||
|
*/
|
||||||
|
public static function split_attr_string($value) {
|
||||||
|
$splited_value = preg_split(
|
||||||
|
'/(?<!\\\\)('.implode('|', self :: $ops).')/',
|
||||||
|
$value,
|
||||||
|
2,
|
||||||
|
PREG_SPLIT_DELIM_CAPTURE
|
||||||
|
);
|
||||||
|
if (!is_array($splited_value) || count($splited_value) != 3)
|
||||||
|
return false;
|
||||||
|
return $splited_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's an operator
|
||||||
|
* @param mixed $value The value to check
|
||||||
|
* @param bool $allow_alias Set if operator alias is allowed (optional, default: true)
|
||||||
|
* @static
|
||||||
|
* @return bool
|
||||||
|
* @phpstan-assert-if-true string $value
|
||||||
|
*/
|
||||||
|
public static function is_op($value, $allow_alias=true) {
|
||||||
|
if (is_string($value) && in_array($value, self :: $ops))
|
||||||
|
return true;
|
||||||
|
if ($allow_alias && is_string($value) && array_key_exists($value, self :: $op_aliases))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's an logical operator
|
||||||
|
* @param mixed $value The value to check
|
||||||
|
* @param bool $allow_alias Set if logical operator alias is allowed (optional, default: true)
|
||||||
|
* @static
|
||||||
|
* @return bool
|
||||||
|
* @phpstan-assert-if-true string $value
|
||||||
|
*/
|
||||||
|
public static function is_log_op($value, $allow_alias=true) {
|
||||||
|
if (is_string($value) && in_array($value, self :: $log_ops))
|
||||||
|
return true;
|
||||||
|
if ($allow_alias && is_string($value) && array_key_exists($value, self :: $log_op_aliases))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve operator alias
|
||||||
|
* @param mixed $value
|
||||||
|
* @static
|
||||||
|
* @return string|false The real operator or false if invalid value specified
|
||||||
|
*/
|
||||||
|
public static function unalias_op($value) {
|
||||||
|
if (is_string($value) && array_key_exists($value, self :: $op_aliases))
|
||||||
|
return self :: $op_aliases[$value];
|
||||||
|
if (self :: is_op($value, false))
|
||||||
|
return $value;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve logical operator alias
|
||||||
|
* @param mixed $value
|
||||||
|
* @static
|
||||||
|
* @return string|false The real operator or false if invalid value specified
|
||||||
|
*/
|
||||||
|
public static function unalias_log_op($value) {
|
||||||
|
if (is_string($value) && array_key_exists($value, self :: $log_op_aliases))
|
||||||
|
return self :: $log_op_aliases[$value];
|
||||||
|
if (self :: is_log_op($value, false))
|
||||||
|
return $value;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's the NOT logical operator
|
||||||
|
* @param mixed $value The value to check
|
||||||
|
* @static
|
||||||
|
* @return bool
|
||||||
|
* @phpstan-assert-if-true string $value
|
||||||
|
*/
|
||||||
|
public static function is_not_op($value) {
|
||||||
|
return $value == self :: $not_op;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's a valid attribute name filter part
|
||||||
|
* @param mixed $value The value to check
|
||||||
|
* @phpstan-assert-if-true string $value
|
||||||
|
* @param bool $allow_extended Allow extended attribute name filter part (optional, default: true)
|
||||||
|
* @static
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function is_attr_name($value, $allow_extended=true) {
|
||||||
|
if (!is_string($value))
|
||||||
|
return false;
|
||||||
|
if (preg_match(self :: $attr_name_regex, $value))
|
||||||
|
return true;
|
||||||
|
if (preg_match(self :: $oid_regex, $value))
|
||||||
|
return true;
|
||||||
|
if ($allow_extended && preg_match(self :: $ext_attr_name_regex, $value))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape a string for LDAP filter pattern
|
||||||
|
* @param string $value
|
||||||
|
* @static
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function escape($value) {
|
||||||
|
return str_replace(array_keys(self :: $espace_chars), array_values(self :: $espace_chars), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unescape a string from LDAP filter pattern
|
||||||
|
* @param string $value
|
||||||
|
* @static
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function unescape($value) {
|
||||||
|
return str_replace(array_values(self :: $espace_chars), array_keys(self :: $espace_chars), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
src/Filter/CombineException.php
Normal file
5
src/Filter/CombineException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyLDAP\Filter;
|
||||||
|
|
||||||
|
class CombineException extends FilterException {}
|
5
src/Filter/FilterException.php
Normal file
5
src/Filter/FilterException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyLDAP\Filter;
|
||||||
|
|
||||||
|
class FilterException extends \Exception {}
|
5
src/Filter/ParserException.php
Normal file
5
src/Filter/ParserException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EesyLDAP\Filter;
|
||||||
|
|
||||||
|
class ParserException extends FilterException {}
|
934
tests/FilterTest.php
Normal file
934
tests/FilterTest.php
Normal file
|
@ -0,0 +1,934 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use EesyLDAP\Filter;
|
||||||
|
use EesyLDAP\Filter\FilterException;
|
||||||
|
use EesyLDAP\Filter\CombineException;
|
||||||
|
use EesyLDAP\Filter\ParserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter
|
||||||
|
*/
|
||||||
|
final class FilterTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithAttrAndOpAndValue() {
|
||||||
|
$a = new Filter('attr', '=', 'value');
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('value', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithAttrAndOpAliasAndValue() {
|
||||||
|
foreach(Filter :: operator_aliases() as $alias => $op) {
|
||||||
|
$a = new Filter('attr', $alias, 'value');
|
||||||
|
$this->assertEquals($op, $a->op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithAttrAndSpecialOpAndValue() {
|
||||||
|
$a = new Filter('attr', 'begins', 'value');
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('value*', $a->pattern);
|
||||||
|
|
||||||
|
$a = new Filter('attr', 'contains', 'value');
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('*value*', $a->pattern);
|
||||||
|
|
||||||
|
$a = new Filter('attr', 'ends', 'value');
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('*value', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithLogOpAndFilters() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
$c2 = new Filter('c', '>=', '2');
|
||||||
|
$a = new Filter('&', $ab, $c2);
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters);
|
||||||
|
$this->assertContains($ab, $a->sub_filters);
|
||||||
|
$this->assertContains($c2, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithLogOpAliasAndFilters() {
|
||||||
|
foreach(Filter :: logical_operator_aliases() as $alias => $op) {
|
||||||
|
$a = new Filter($alias, new Filter('a', '=', 'b'));
|
||||||
|
$this->assertEquals($op, $a->log_op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithLogOpAndArrayOfFilters() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
$c2 = new Filter('c', '>=', '2');
|
||||||
|
$bd = new Filter('b', '=', 'd');
|
||||||
|
$a = new Filter('&', array($ab, $c2), $bd);
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(3, $a->sub_filters);
|
||||||
|
$this->assertContains($ab, $a->sub_filters);
|
||||||
|
$this->assertContains($c2, $a->sub_filters);
|
||||||
|
$this->assertContains($bd, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithLogOpAndArrayOfFilterStrings() {
|
||||||
|
$a = new Filter('&', array('a=b', '(c>=2)'), 'b~=test');
|
||||||
|
$this->assertEquals($a->as_string(), '(&(a=b)(c>=2)(b~=test))');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithLogOpAndMixedFilterTypes() {
|
||||||
|
$a = new Filter(
|
||||||
|
'|',
|
||||||
|
array(new Filter('a', '=', 'b'), 'c>=2'),
|
||||||
|
new Filter('b', '~=', 'test'),
|
||||||
|
'd=z'
|
||||||
|
);
|
||||||
|
$this->assertEquals($a->as_string(), '(|(a=b)(c>=2)(b~=test)(d=z))');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithNotOpAndFilter() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
$a = new Filter('!', $ab);
|
||||||
|
$this->assertEquals('!', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(1, $a->sub_filters);
|
||||||
|
$this->assertContains($ab, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithNotOpAndMoreThanOneFilter() {
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$a = new Filter('!', new Filter('a', '=', 'b'), new Filter('c', '>=', '2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithPresentAnyOp() {
|
||||||
|
$a = new Filter('a', 'present');
|
||||||
|
$this->assertEquals('a', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('*', $a->pattern);
|
||||||
|
|
||||||
|
$a = new Filter('a', 'any');
|
||||||
|
$this->assertEquals('a', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('*', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithEscapeArg() {
|
||||||
|
$a = new Filter('attr', '=', 'value*', true);
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('value\2a', $a->pattern);
|
||||||
|
|
||||||
|
$a = new Filter('attr', '=', 'value*', False);
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('value*', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithoutArg() {
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$a = new Filter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructCombineWithInvalidFilter() {
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$a = new Filter('&', 'a=b', new FilterException('test'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithInvalidThreeArgs() {
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$a = new Filter('a', 'b', 'c');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__construct
|
||||||
|
*/
|
||||||
|
public function testConstructWithInvalidTwoArgs() {
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
$a = new Filter('a', '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__get()
|
||||||
|
*/
|
||||||
|
public function testGetOnLeafFilter() {
|
||||||
|
$a = new Filter('attr', '=', 'value');
|
||||||
|
$this->assertEquals('attr', $a->attr_name);
|
||||||
|
$this->assertEquals('attr', $a->attribute);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('=', $a->operator);
|
||||||
|
$this->assertEquals('value', $a->pattern);
|
||||||
|
$this->assertNull($a->log_op);
|
||||||
|
$this->assertNull($a->logical_operator);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertEmpty($a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__get
|
||||||
|
*/
|
||||||
|
public function testGetOnCombineFilter() {
|
||||||
|
$a = new Filter('&', 'attr=value');
|
||||||
|
$this->assertNull($a->attr_name);
|
||||||
|
$this->assertNull($a->attribute);
|
||||||
|
$this->assertNull($a->op);
|
||||||
|
$this->assertNull($a->operator);
|
||||||
|
$this->assertNull($a->pattern);
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertEquals('&', $a->logical_operator);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(1, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::__get
|
||||||
|
*/
|
||||||
|
public function testGetOnUndefinedProperty() {
|
||||||
|
$a = new Filter('&', 'attr=value');
|
||||||
|
$this->expectException(FilterException::class);
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$a->undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::operators
|
||||||
|
*/
|
||||||
|
public function testOperators() {
|
||||||
|
$ops = Filter::operators();
|
||||||
|
$this->assertIsArray($ops);
|
||||||
|
$this->assertNotEmpty($ops);
|
||||||
|
foreach($ops as $op) {
|
||||||
|
$this->assertIsString($op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::operator_aliases
|
||||||
|
*/
|
||||||
|
public function testOperatorAliases() {
|
||||||
|
$op_aliases = Filter::operator_aliases();
|
||||||
|
$this->assertIsArray($op_aliases);
|
||||||
|
$this->assertNotEmpty($op_aliases);
|
||||||
|
foreach($op_aliases as $alias => $op) {
|
||||||
|
$this->assertIsString($alias);
|
||||||
|
$this->assertIsString($op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::logical_operators
|
||||||
|
*/
|
||||||
|
public function testLogicalOperators() {
|
||||||
|
$log_ops = Filter::logical_operators();
|
||||||
|
$this->assertIsArray($log_ops);
|
||||||
|
$this->assertNotEmpty($log_ops);
|
||||||
|
foreach($log_ops as $op) {
|
||||||
|
$this->assertIsString($op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::logical_operator_aliases
|
||||||
|
*/
|
||||||
|
public function testLogicalOperatorAliases() {
|
||||||
|
$log_op_aliases = Filter::logical_operator_aliases();
|
||||||
|
$this->assertIsArray($log_op_aliases);
|
||||||
|
$this->assertNotEmpty($log_op_aliases);
|
||||||
|
foreach($log_op_aliases as $alias => $op) {
|
||||||
|
$this->assertIsString($alias);
|
||||||
|
$this->assertIsString($op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_leaf
|
||||||
|
*/
|
||||||
|
public function testIsLeaf() {
|
||||||
|
$a = new Filter('attr', '=', 'value');
|
||||||
|
$this->assertTrue($a->is_leaf());
|
||||||
|
$a = new Filter('!', 'a=b');
|
||||||
|
$this->assertFalse($a->is_leaf());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_combine
|
||||||
|
*/
|
||||||
|
public function testIsCombine() {
|
||||||
|
$a = new Filter('attr', '=', 'value');
|
||||||
|
$this->assertFalse($a->is_combine());
|
||||||
|
$a = new Filter('!', 'a=b');
|
||||||
|
$this->assertTrue($a->is_combine());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::as_string
|
||||||
|
*/
|
||||||
|
public function testLeafFilterAsString() {
|
||||||
|
$a = new Filter('attr', '=', 'value');
|
||||||
|
$this->assertEquals('(attr=value)', $a->as_string(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::as_string
|
||||||
|
*/
|
||||||
|
public function testLeafFilterAsPrettyString() {
|
||||||
|
$a = new Filter('attr', '=', 'value');
|
||||||
|
$this->assertEquals('(attr=value)', $a->as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineFilterAsString() {
|
||||||
|
$a = new Filter('&', 'a=b', 'c=d');
|
||||||
|
$this->assertEquals('(&(a=b)(c=d))', $a->as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineFilterAsPrettyString() {
|
||||||
|
$a = new Filter('&', 'a=b', 'c=d');
|
||||||
|
$this->assertEquals("(&\n\t(a=b)\n\t(c=d)\n)", $a->as_string(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineStringFilters() {
|
||||||
|
$a = Filter :: combine('&', 'a=b', 'c=d');
|
||||||
|
$this->assertEquals('(&(a=b)(c=d))', $a->as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineFilters() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
$bd = new Filter('b', '=', 'd');
|
||||||
|
$a = Filter :: combine('&', $ab, $bd);
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters);
|
||||||
|
$this->assertContains($ab, $a->sub_filters);
|
||||||
|
$this->assertContains($bd, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineWithLogOpAlias() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
foreach(Filter :: logical_operator_aliases() as $alias => $op) {
|
||||||
|
$a = Filter::combine($alias, $ab);
|
||||||
|
$this->assertEquals($op, $a->log_op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineArrayOfFilters() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
$c2 = new Filter('c', '>=', '2');
|
||||||
|
$bd = new Filter('b', '=', 'd');
|
||||||
|
$a = Filter::combine('&', array($ab, $c2), $bd);
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(3, $a->sub_filters);
|
||||||
|
$this->assertContains($ab, $a->sub_filters);
|
||||||
|
$this->assertContains($c2, $a->sub_filters);
|
||||||
|
$this->assertContains($bd, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineArrayOfFilterStrings() {
|
||||||
|
$a = Filter::combine('&', array('a=b', '(c>=2)'), 'b~=test');
|
||||||
|
$this->assertEquals($a->as_string(), '(&(a=b)(c>=2)(b~=test))');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineMixedFilterTypes() {
|
||||||
|
$a = Filter::combine(
|
||||||
|
'|',
|
||||||
|
array(new Filter('a', '=', 'b'), 'c>=2'),
|
||||||
|
new Filter('b', '~=', 'test'),
|
||||||
|
'd=z'
|
||||||
|
);
|
||||||
|
$this->assertEquals($a->as_string(), '(|(a=b)(c>=2)(b~=test)(d=z))');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineNotOpAndFilter() {
|
||||||
|
$ab = new Filter('a', '=', 'b');
|
||||||
|
$a = Filter::combine('!', $ab);
|
||||||
|
$this->assertEquals('!', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(1, $a->sub_filters);
|
||||||
|
$this->assertContains($ab, $a->sub_filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineNotOpAndMoreThanOneFilter() {
|
||||||
|
$this->expectException(CombineException::class);
|
||||||
|
$a = Filter::combine('!', new Filter('a', '=', 'b'), new Filter('c', '>=', '2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineInvalidLogOp() {
|
||||||
|
$this->expectException(CombineException::class);
|
||||||
|
$a = Filter::combine('X', new Filter('a', '=', 'b'), new Filter('c', '>=', '2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::combine
|
||||||
|
*/
|
||||||
|
public function testCombineInvalidFilter() {
|
||||||
|
$this->expectException(CombineException::class);
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$a = Filter::combine('&', new Filter('a', '=', 'b'), new FilterException('test'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::split_attr_string
|
||||||
|
*/
|
||||||
|
public function testSplitAttrString() {
|
||||||
|
$this-> assertEquals(array('a', '=', 'b'), Filter::split_attr_string('a=b'));
|
||||||
|
$this-> assertEquals(array('owner', '=', 'cn=admin,o=example'), Filter::split_attr_string('owner=cn=admin,o=example'));
|
||||||
|
$this-> assertFalse(Filter::split_attr_string('owner'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_op
|
||||||
|
*/
|
||||||
|
public function test_is_op() {
|
||||||
|
foreach(Filter::operators() as $op)
|
||||||
|
$this -> assertTrue(Filter::is_op($op));
|
||||||
|
foreach(Filter::operator_aliases() as $alias => $op) {
|
||||||
|
$this -> assertTrue(Filter::is_op($alias));
|
||||||
|
$this -> assertTrue(Filter::is_op($op));
|
||||||
|
}
|
||||||
|
$this -> assertFalse(Filter::is_op('fake_op', false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_op
|
||||||
|
*/
|
||||||
|
public function test_is_op_without_alias() {
|
||||||
|
foreach(Filter::operators() as $op)
|
||||||
|
$this -> assertTrue(Filter::is_op($op, false));
|
||||||
|
foreach(Filter::operator_aliases() as $alias => $op) {
|
||||||
|
$this -> assertFalse(Filter::is_op($alias, false));
|
||||||
|
$this -> assertTrue(Filter::is_op($op, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_log_op
|
||||||
|
*/
|
||||||
|
public function test_is_log_op() {
|
||||||
|
foreach(Filter::logical_operators() as $op)
|
||||||
|
$this -> assertTrue(Filter::is_log_op($op));
|
||||||
|
foreach(Filter::logical_operator_aliases() as $alias => $op) {
|
||||||
|
$this -> assertTrue(Filter::is_log_op($alias));
|
||||||
|
$this -> assertTrue(Filter::is_log_op($op));
|
||||||
|
}
|
||||||
|
$this -> assertFalse(Filter::is_log_op('fake_op', false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_log_op
|
||||||
|
*/
|
||||||
|
public function test_is_log_op_without_alias() {
|
||||||
|
foreach(Filter::logical_operators() as $op)
|
||||||
|
$this -> assertTrue(Filter::is_log_op($op, false));
|
||||||
|
foreach(Filter::logical_operator_aliases() as $alias => $op) {
|
||||||
|
$this -> assertFalse(Filter::is_log_op($alias, false));
|
||||||
|
$this -> assertTrue(Filter::is_log_op($op, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::unalias_op
|
||||||
|
*/
|
||||||
|
public function test_unalias_op() {
|
||||||
|
foreach(Filter::operators() as $op)
|
||||||
|
$this -> assertEquals($op, Filter::unalias_op($op));
|
||||||
|
foreach(Filter::operator_aliases() as $alias => $op) {
|
||||||
|
$this -> assertEquals($op, Filter::unalias_op($alias));
|
||||||
|
$this -> assertEquals($op, Filter::unalias_op($op));
|
||||||
|
}
|
||||||
|
$this -> assertFalse(Filter::unalias_op('fake_op'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::unalias_log_op
|
||||||
|
*/
|
||||||
|
public function test_unalias_log_op() {
|
||||||
|
foreach(Filter::logical_operators() as $op)
|
||||||
|
$this -> assertEquals($op, Filter::unalias_log_op($op));
|
||||||
|
foreach(Filter::logical_operator_aliases() as $alias => $op) {
|
||||||
|
$this -> assertEquals($op, Filter::unalias_log_op($alias));
|
||||||
|
$this -> assertEquals($op, Filter::unalias_log_op($op));
|
||||||
|
}
|
||||||
|
$this -> assertFalse(Filter::unalias_log_op('fake_op'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_not_op
|
||||||
|
*/
|
||||||
|
public function test_is_not_op() {
|
||||||
|
$this -> assertTrue(Filter :: is_not_op('!'));
|
||||||
|
$this -> assertFalse(Filter :: is_not_op('&'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_attr_name
|
||||||
|
*/
|
||||||
|
public function test_is_attr_name() {
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('o'));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn'));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn;x-test'));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn;x-test;x-test2'));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn;x-test;x-test2;x-test3'));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('1.12.34'));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('1.12.34.'));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('1'));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('1.'));
|
||||||
|
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('bad name'));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('1word'));
|
||||||
|
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('cn:', false));
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_attr_name
|
||||||
|
*/
|
||||||
|
public function test_is_attr_name_allow_extended_by_default() {
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:dn:'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::is_attr_name
|
||||||
|
*/
|
||||||
|
public function test_is_attr_name_extended() {
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('o', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn', true));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn;x-test', true));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:', true));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:dn:', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:DN:', true));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:dn:caseExactMatch:', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:DN:caseExactMatch:', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:dn:2.4.6.8.10:', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:DN:2.4.6.8.10:', true));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name(':DN:caseExactMatch:', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name(':DN:2.4.6.8.10:', true));
|
||||||
|
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:caseExactMatch:', true));
|
||||||
|
$this -> assertTrue(Filter :: is_attr_name('cn:2.4.6.8.10:', true));
|
||||||
|
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('bad name', true));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('cn:dn', true));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('cn:DN', true));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('cn:case10', true));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('cn:case10:', true));
|
||||||
|
$this -> assertFalse(Filter :: is_attr_name('cn:test1:test2:', true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::escape
|
||||||
|
*/
|
||||||
|
public function testEscape(){
|
||||||
|
$this->assertEquals('test\2a', Filter::escape('test*'));
|
||||||
|
$this->assertEquals('\28test\29', Filter::escape('(test)'));
|
||||||
|
$this->assertEquals('\5ctest', Filter::escape('\\test'));
|
||||||
|
$this->assertEquals('\00', Filter::escape("\u{0000}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::unescape
|
||||||
|
*/
|
||||||
|
public function testUnescape(){
|
||||||
|
$this->assertEquals('test*test', Filter::unescape('test\2atest'));
|
||||||
|
$this->assertEquals('(test)', Filter::unescape('\28test\29'));
|
||||||
|
$this->assertEquals('\test', Filter::unescape('\\5ctest'));
|
||||||
|
$this->assertEquals("\u{0000}", Filter::unescape('\00'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Freely inspired by test cases provided with butonic's php-ldap-filter-parser.
|
||||||
|
* @see: https://github.com/butonic/php-ldap-filter-parser
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter() {
|
||||||
|
$a = Filter::parse('(cn=Babs Jensen)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('cn', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Babs Jensen', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter_without_brackets() {
|
||||||
|
$a = Filter::parse('cn=Babs Jensen');
|
||||||
|
$b = Filter::parse('(cn=Babs Jensen)');
|
||||||
|
$this->assertEquals($a, $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter_with_one_asterisk() {
|
||||||
|
$a = Filter::parse('(cn=Babs *)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('cn', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Babs *', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter_with_multiple_asterisk() {
|
||||||
|
$a = Filter::parse('(o=univ*of*mich*)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('o', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('univ*of*mich*', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter_with_empty_value() {
|
||||||
|
$a = Filter::parse('(seeAlso=)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('seeAlso', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended attribute name filters
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter_with_extended_attr_name() {
|
||||||
|
$a = Filter::parse('(cn:caseExactMatch:=Fred Flintstone)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('cn:caseExactMatch:', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Fred Flintstone', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(cn:=Betty Rubble)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('cn:', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Betty Rubble', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(sn:dn:2.4.6.8.10:=Betty Rubble)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('sn:dn:2.4.6.8.10:', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Betty Rubble', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(o:dn:=Ace Industry:)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('o:dn:', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Ace Industry:', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(:1.2.3:=Wilma Flintstone)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals(':1.2.3:', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Wilma Flintstone', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(:DN:2.4.6.8.10:=Dino)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals(':DN:2.4.6.8.10:', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Dino', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escaped characters
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_leaf_filter_with_escaped_characters() {
|
||||||
|
$a = Filter::parse('(o=Parens R Us \\28for all your parenthetical needs\\29)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('o', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Parens R Us \\28for all your parenthetical needs\\29', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(cn=*\\2A*)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('cn', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('*\\2A*', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(filename=C:\\5cMyFile)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('filename', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('C:\\5cMyFile', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(bin=\\00\\00\\00\\04)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('bin', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('\\00\\00\\00\\04', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(sn=Lu\\c4\\8di\\c4\\87)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('sn', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('Lu\\c4\\8di\\c4\\87', $a->pattern);
|
||||||
|
|
||||||
|
$a = Filter::parse('(1.3.6.1.4.1.1466.0=\\04\\02\\48\\69)');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertEquals('1.3.6.1.4.1.1466.0', $a->attr_name);
|
||||||
|
$this->assertEquals('=', $a->op);
|
||||||
|
$this->assertEquals('\\04\\02\\48\\69', $a->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse combined filters
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_combined_filter() {
|
||||||
|
$a = Filter::parse('(&(objectClass=inetOrgPerson)(memberOf=cn=owncloudusers,ou=groups,dc=example,dc=com))');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertTrue($a->is_combine());
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[0]->is_leaf());
|
||||||
|
$this->assertEquals('objectClass', $a->sub_filters[0]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[0]->op);
|
||||||
|
$this->assertEquals('inetOrgPerson', $a->sub_filters[0]->pattern);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->is_leaf());
|
||||||
|
$this->assertEquals('memberOf', $a->sub_filters[1]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[1]->op);
|
||||||
|
$this->assertEquals('cn=owncloudusers,ou=groups,dc=example,dc=com', $a->sub_filters[1]->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse multiple combined filters
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_multiple_combined_filters() {
|
||||||
|
$a = Filter::parse('(&(|(uid=a*)(userid=a*))(|(telephoneNumber=1234)(mobile=1234)))');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertTrue($a->is_combine());
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[0]->is_combine());
|
||||||
|
$this->assertEquals('|', $a->sub_filters[0]->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters[0]->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters[0]->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[0]->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[0]->sub_filters[0]->is_leaf());
|
||||||
|
$this->assertEquals('uid', $a->sub_filters[0]->sub_filters[0]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[0]->sub_filters[0]->op);
|
||||||
|
$this->assertEquals('a*', $a->sub_filters[0]->sub_filters[0]->pattern);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[0]->sub_filters[1]);
|
||||||
|
$this->assertTrue($a->sub_filters[0]->sub_filters[1]->is_leaf());
|
||||||
|
$this->assertEquals('userid', $a->sub_filters[0]->sub_filters[1]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[0]->sub_filters[1]->op);
|
||||||
|
$this->assertEquals('a*', $a->sub_filters[0]->sub_filters[1]->pattern);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->is_combine());
|
||||||
|
$this->assertEquals('|', $a->sub_filters[1]->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters[1]->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters[1]->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->sub_filters[0]->is_leaf());
|
||||||
|
$this->assertEquals('telephoneNumber', $a->sub_filters[1]->sub_filters[0]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[1]->sub_filters[0]->op);
|
||||||
|
$this->assertEquals('1234', $a->sub_filters[1]->sub_filters[0]->pattern);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]->sub_filters[1]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->sub_filters[1]->is_leaf());
|
||||||
|
$this->assertEquals('mobile', $a->sub_filters[1]->sub_filters[1]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[1]->sub_filters[1]->op);
|
||||||
|
$this->assertEquals('1234', $a->sub_filters[1]->sub_filters[1]->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse multiple combined and nested filters
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_multiple_combined_and_nested_filters() {
|
||||||
|
$a = Filter::parse('(&(objectClass=Person)(|(sn=Jensen)(cn=Babs J*)))');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertTrue($a->is_combine());
|
||||||
|
$this->assertEquals('&', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[0]->is_leaf());
|
||||||
|
$this->assertEquals('objectClass', $a->sub_filters[0]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[0]->op);
|
||||||
|
$this->assertEquals('Person', $a->sub_filters[0]->pattern);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->is_combine());
|
||||||
|
$this->assertEquals('|', $a->sub_filters[1]->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters[1]->sub_filters);
|
||||||
|
$this->assertCount(2, $a->sub_filters[1]->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->sub_filters[0]->is_leaf());
|
||||||
|
$this->assertEquals('sn', $a->sub_filters[1]->sub_filters[0]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[1]->sub_filters[0]->op);
|
||||||
|
$this->assertEquals('Jensen', $a->sub_filters[1]->sub_filters[0]->pattern);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[1]->sub_filters[1]);
|
||||||
|
$this->assertTrue($a->sub_filters[1]->sub_filters[1]->is_leaf());
|
||||||
|
$this->assertEquals('cn', $a->sub_filters[1]->sub_filters[1]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[1]->sub_filters[1]->op);
|
||||||
|
$this->assertEquals('Babs J*', $a->sub_filters[1]->sub_filters[1]->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a not filter
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_not_filter() {
|
||||||
|
$a = Filter::parse('(!(objectClass=Person))');
|
||||||
|
$this->assertInstanceOf(Filter::class, $a);
|
||||||
|
$this->assertTrue($a->is_combine());
|
||||||
|
$this->assertEquals('!', $a->log_op);
|
||||||
|
$this->assertIsArray($a->sub_filters);
|
||||||
|
$this->assertCount(1, $a->sub_filters);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Filter::class, $a->sub_filters[0]);
|
||||||
|
$this->assertTrue($a->sub_filters[0]->is_leaf());
|
||||||
|
$this->assertEquals('objectClass', $a->sub_filters[0]->attr_name);
|
||||||
|
$this->assertEquals('=', $a->sub_filters[0]->op);
|
||||||
|
$this->assertEquals('Person', $a->sub_filters[0]->pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse filter with unclosed bracket
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_filter_with_unclosed_bracket() {
|
||||||
|
$this->expectException(ParserException::class);
|
||||||
|
Filter::parse('(objectClass=Person');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse NOT filter with multiple sub filters
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_not_filter_with_multiple_filter() {
|
||||||
|
$this->expectException(ParserException::class);
|
||||||
|
Filter::parse('(!(objectClass=Person)(cn=test))');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse filter with unescaped brackets
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_with_unescaped_brackets() {
|
||||||
|
$this->expectException(ParserException::class);
|
||||||
|
var_dump(Filter::parse('(cn=test(test))'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse filter with invalid sub filter
|
||||||
|
* @covers \EesyLDAP\Filter::parse
|
||||||
|
*/
|
||||||
|
public function test_parse_with_invalid_sub_filter() {
|
||||||
|
$this->expectException(ParserException::class);
|
||||||
|
var_dump(Filter::parse('(&(cn=test)(test))'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue