File Coverage

blib/lib/Net/LDAP/FilterMatch.pm
Criterion Covered Total %
statement 171 200 85.5
branch 77 132 58.3
condition 5 12 41.6
subroutine 20 23 86.9
pod 0 1 0.0
total 273 368 74.1


line stmt bran cond sub pod time code
1             # Net::LDAP::FilterMatch
2             #
3             # LDAP entry matching
4             #
5             # Copyright (c) 2005-2006 Hans Klunder
6             # Copyright (c) 2005-2012 Peter Marschall
7             #
8             # See below for documentation.
9             #
10              
11             package Net::LDAP::FilterMatch;
12              
13 1     1   422 use strict;
  1         2  
  1         26  
14 1     1   5 use Net::LDAP::Filter;
  1         2  
  1         23  
15 1     1   366 use Net::LDAP::Schema;
  1         2  
  1         93  
16              
17             our $VERSION = '0.27';
18              
19             sub import {
20 1     1   11 shift;
21              
22 1 50       5 push(@_, @Net::LDAP::Filter::approxMatchers) unless @_;
23 1         2 @Net::LDAP::Filter::approxMatchers = grep { eval "require $_" } @_ ;
  1         55  
24             }
25              
26             package Net::LDAP::Filter;
27              
28 1     1   3397 use Net::LDAP::Util qw(canonical_dn ldap_explode_dn);
  1         2  
  1         4205  
29              
30             our @approxMatchers = qw(
31             String::Approx
32             Text::Metaphone
33             Text::Soundex
34             );
35              
36             sub _filterMatch($@);
37              
38             # specific matching rules
39             sub _booleanMatch($$@);
40             sub _distinguishedNameMatch($$@);
41             sub _integerBitAndMatch($$@);
42             sub _integerBitOrMatch($$@);
43              
44             # generic matching rules
45             sub _cis_equalityMatch($$@);
46             sub _exact_equalityMatch($$@);
47             sub _numeric_equalityMatch($$@);
48             sub _tel_equalityMatch($$@);
49             sub _cis_orderingMatch($$@);
50             sub _numeric_orderingMatch($$@);
51             sub _cis_greaterOrEqual($$@);
52             sub _cis_lessOrEqual($$@);
53             sub _cis_approxMatch($$@);
54             sub _cis_substrings($$@);
55             sub _exact_substrings($$@);
56             sub _tel_substrings($$@);
57              
58             # all known matches from the OL 2.4 schema,
59             #*_allComponentsMatch
60             *_attributeCertificateExactMatch = \&_exact_equalityMatch;
61             *_attributeCertificateMatch = \&_exact_equalityMatch;
62             *_authPasswordMatch = \&_exact_equalityMatch; # this needs to be reworked
63             *_authzMatch = \&_exact_equalityMatch;
64             *_bitStringMatch = \&_exact_equalityMatch;
65             *_caseExactIA5Match = \&_exact_equalityMatch;
66             *_caseExactIA5SubstringsMatch = \&_exact_substrings;
67             *_caseExactMatch = \&_exact_equalityMatch;
68             *_caseExactOrderingMatch = \&_exact_orderingMatch;
69             *_caseExactSubstringsMatch = \&_exact_substrings;
70             *_caseIgnoreIA5Match = \&_cis_equalityMatch;
71             *_caseIgnoreIA5SubstringsMatch = \&_cis_substrings;
72             *_caseIgnoreListMatch = \&_cis_equalityMatch; # this needs to be reworked
73             *_caseIgnoreListSubstringsMatch = \&_cis_substrings; # this needs to be reworked
74             *_caseIgnoreMatch = \&_cis_equalityMatch;
75             *_caseIgnoreOrderingMatch = \&_cis_orderingMatch;
76             *_caseIgnoreSubstringsMatch = \&_cis_substrings;
77             *_certificateExactMatch = \&_exact_equalityMatch;
78             *_certificateListExactMatch = \&_exact_equalityMatch; # this needs to be reworked
79             *_certificateListMatch = \&_exact_equalityMatch; # this needs to be reworked
80             *_certificateMatch = \&_exact_equalityMatch;
81             #*_componentFilterMatch
82             *_CSNMatch = \&_exact_equalityMatch; # this may need to be reworked
83             *_CSNOrderingMatch = \&_exact_orderingMatch; # this may need to be reworked
84             *_CSNSIDMatch = \&_exact_equalityMatch; # this may need to be reworked
85             #*_directoryComponentsMatch
86             *_directoryStringApproxMatch = \&_cis_approxMatch;
87             #*_dnOneLevelMatch
88             #*_dnSubordinateMatch
89             #*_dnSubtreeMatch
90             #*_dnSuperiorMatch
91             *_facsimileNumberMatch = \&_tel_equalityMatch;
92             *_facsimileNumberSubstringsMatch = \&_tel_substrings;
93             *_generalizedTimeMatch = \&_exact_equalityMatch;
94             *_generalizedTimeOrderingMatch = \&_exact_orderingMatch;
95             *_IA5StringApproxMatch = \&_cis_approxMatch;
96             *_integerFirstComponentMatch = \&_exact_equalityMatch;
97             *_integerMatch = \&_numeric_equalityMatch;
98             *_integerOrderingMatch = \&_numeric_orderingMatch;
99             *_numericStringMatch = \&_numeric_equalityMatch;
100             *_numericStringOrderingMatch = \&_numeric_orderingMatch;
101             *_numericStringSubstringsMatch = \&_numeric_substrings;
102             *_objectIdentifierFirstComponentMatch = \&_exact_equalityMatch; # this needs to be reworked
103             *_objectIdentifierMatch = \&_cis_equalityMatch;
104             *_octetStringMatch = \&_exact_equalityMatch;
105             *_octetStringOrderingMatch = \&_exact_orderingMatch;
106             *_octetStringSubstringsMatch = \&_exact_substrings;
107             #*_presentationAddressMatch
108             #*_protocolInformationMatch
109             #*_rdnMatch
110             *_telephoneNumberMatch = \&_tel_equalityMatch;
111             *_telephoneNumberSubstringsMatch = \&_tel_substrings;
112             *_uniqueMemberMatch = \&_cis_equalityMatch; # this needs to be reworked
113             *_UUIDMatch = \&_exact_equalityMatch; # this needs to be reworked
114             *_UUIDOrderingMatch = \&_exact_orderingMatch; # this needs to be reworked
115              
116             sub match
117             {
118 100     100 0 30213 my $self = shift;
119 100         142 my $entry = shift;
120 100         137 my $schema =shift;
121              
122 100         218 return _filterMatch($self, $entry, $schema);
123             }
124              
125             # map Ops to schema matches
126             my %op2schema = qw(
127             equalityMatch equality
128             greaterOrEqual ordering
129             lessOrEqual ordering
130             approxMatch approx
131             substrings substr
132             );
133              
134             sub _filterMatch($@)
135             {
136 106     106   122 my $filter = shift;
137 106         108 my $entry = shift;
138 106         114 my $schema = shift;
139              
140 106         117 keys(%{$filter}); # re-initialize each() operator
  106         171  
141 106         130 my ($op, $args) = each(%{$filter});
  106         203  
142              
143             # handle combined filters
144 106 50       219 if ($op eq 'and') { # '(&()...)' => fail on 1st mismatch
145 0         0 foreach my $subfilter (@{$args}) {
  0         0  
146 0 0       0 return 0 if (!_filterMatch($subfilter, $entry));
147             }
148 0         0 return 1; # all matched or '(&)' => succeed
149             }
150 106 50       159 if ($op eq 'or') { # '(|()...)' => succeed on 1st match
151 0         0 foreach my $subfilter (@{$args}) {
  0         0  
152 0 0       0 return 1 if (_filterMatch($subfilter, $entry));
153             }
154 0         0 return 0; # none matched or '(|)' => fail
155             }
156 106 100       149 if ($op eq 'not') {
157 6         15 return (! _filterMatch($args, $entry));
158             }
159 100 100       146 if ($op eq 'present') {
160             #return 1 if (lc($args) eq 'objectclass'); # "all match" filter
161 2         9 return ($entry->exists($args));
162             }
163              
164             # handle basic filters
165 98 100       302 if ($op =~ /^(equalityMatch|greaterOrEqual|lessOrEqual|approxMatch|substrings)/o) {
    50          
166 84         192 my $attr;
167             my $assertion;
168 84         0 my $match;
169              
170 84 100       141 if ($op eq 'substrings') {
171 22         32 $attr = $args->{type};
172             # build a regexp as assertion value
173 22         30 $assertion = join('.*', map { "\Q$_\E" } map { values %$_ } @{$args->{substrings}});
  38         79  
  38         85  
  22         38  
174 22 100       58 $assertion = '^'. $assertion if (exists $args->{substrings}[0]{initial});
175 22 100       42 $assertion .= '$' if (exists $args->{substrings}[-1]{final});
176             }
177             else {
178 62         98 $attr = $args->{attributeDesc};
179             $assertion = $args->{assertionValue}
180 62         83 }
181              
182 84         220 my @values = $entry->get_value($attr);
183              
184             # approx match is not standardized in schema
185 84 100 100     291 if ($schema and ($op ne 'approxMatch')) {
186             # get matchingrule from schema, be sure that matching subs exist for every MR in your schema
187 31         84 my $mr = $schema->matchingrule_for_attribute($attr, $op2schema{$op});
188 31 100       60 return undef if (!$mr);
189 29         46 $match = '_' . $mr;
190             }
191             else {
192             # fall back on build-in logic
193 53         66 $match='_cis_' . $op;
194             }
195              
196 82         4511 return eval( "$match".'($assertion, $op, @values)' ) ;
197             }
198             elsif ($op eq 'extensibleMatch') {
199 14 100       33 my @attrs = $args->{type} ? ( $args->{type} ) : ();
200 14         21 my $assertion = $args->{matchValue};
201 14         19 my $match;
202             my @values;
203              
204 14 100       22 if ($schema) {
205 5         7 my $mr;
206              
207             # get matchingrule from schema, be sure that matching subs exist for every MR in your schema
208 5 100       12 if (defined($args->{matchingRule})) {
209 4         13 my $mrhref = $schema->matchingrule($args->{matchingRule});
210 4 50       13 $mr = $mrhref->{name} if ($mrhref);
211             # if no attribute was given, get all attribute this matching rule applies to
212 4 100       10 if (!@attrs) {
213 2         6 my $mruhref = $schema->matchingruleuse($args->{matchingRule});
214 2 50       5 return undef if (!$mruhref);
215 2         3 @attrs = @{$mruhref->{applies}};
  2         39  
216             }
217             }
218             else {
219 1 50       4 return undef if (!@attrs);
220 1         5 $mr = $schema->matchingrule_for_attribute($attrs[0], 'equality');
221             }
222              
223 5 50       10 return undef if (!$mr);
224 5         9 $match = '_'.$mr;
225             }
226             else {
227             # fall back on build-in logic
228 9         14 $match = '_cis_equalityMatch';
229             }
230              
231 14 100       21 if ($args->{dnAttributes}) {
232             # get matching attributes' values from DN
233 4         15 my $exploded = ldap_explode_dn($entry->dn, casefold => 'lower');
234 4         6 my %dnattrs;
235 4 50       10 return undef if (!$exploded);
236 4         5 foreach my $elem (@{$exploded}) {
  4         8  
237 20         23 map { push(@{$dnattrs{$_}}, $elem->{$_}) } keys(%{$elem});
  20         23  
  20         55  
  20         47  
238             }
239 4 100       11 @values = map { ($dnattrs{$_}) ? @{$dnattrs{$_}} : () } (@attrs) ? @attrs : keys(%dnattrs);
  127 100       162  
  10         24  
240             }
241             else {
242             # regular case: get matching attributes' values
243 10 100       20 return undef if (!@attrs);
244 9         15 @values = map { $entry->get_value($_); } @attrs;
  129         189  
245             }
246              
247 13         707 return eval( "$match".'($assertion, $op, @values)' ) ;
248             }
249              
250 0         0 return undef; # all other filters => fail with error
251             }
252              
253             # specific matching rules
254              
255             sub _booleanMatch($$@)
256             {
257 0     0   0 my $assertion = shift;
258 0         0 my $op = shift;
259              
260 0 0       0 return undef if ($assertion !~ /^(?:TRUE|FALSE)$/i);
261 0 0 0     0 return 1 if (!@_ && $assertion =~ /^FALSE$/i);
262 0 0       0 return grep(/^\Q$assertion\E$/i, @_) ? 1 : 0;
263             }
264              
265             sub _distinguishedNameMatch($$@)
266             {
267 1     1   5 my $assertion = canonical_dn(shift);
268 1         3 my $op = shift;
269 1         2 my @vals = map { canonical_dn($_) } @_;
  1         3  
270              
271 1 50       4 return undef if (!defined($assertion));
272 1 50       27 return grep(/^\Q$assertion\E$/i, @vals) ? 1 : 0;
273             }
274              
275             sub _integerBitAndMatch($$@)
276             {
277 1     1   3 my $assertion = shift;
278 1         3 my $op = shift;
279 1         5 my @vals = grep(/^-?\d+$/, @_);
280              
281 1 50       3 return (grep { ($assertion & $_) == $assertion } @vals) ? 1 : 0;
  1         12  
282             }
283              
284             sub _integerBitOrMatch($$@)
285             {
286 1     1   3 my $assertion = shift;
287 1         2 my $op = shift;
288 1         5 my @vals = grep(/^-?\d+$/, @_);
289              
290 1 50       3 return (grep { ($assertion & $_) != 0 } @vals) ? 1 : 0;
  1         12  
291             }
292              
293             # generic matching rules
294              
295             sub _cis_equalityMatch($$@)
296             {
297 32     32   58 my $assertion = shift;
298 32         42 my $op = shift;
299              
300 32 100       603 return grep(/^\Q$assertion\E$/i, @_) ? 1 : 0;
301             }
302              
303             sub _exact_equalityMatch($$@)
304             {
305 0     0   0 my $assertion = shift;
306 0         0 my $op = shift;
307              
308 0 0       0 return grep(/^\Q$assertion\E$/, @_) ? 1 : 0;
309             }
310              
311             sub _numeric_equalityMatch($$@)
312             {
313 2     2   4 my $assertion = shift;
314 2         5 my $op = shift;
315              
316 2 100       40 return grep(/^\Q$assertion\E$/, @_) ? 1 : 0;
317             }
318              
319             sub _tel_equalityMatch($$@)
320             {
321 3     3   5 my $assertion = shift;
322 3         4 my $op = shift;
323 3         8 my @vals = map { s/\+/00/; s/\D//g; $_ } grep { /^\+?[\d\s-]+$/ } @_;
  3         9  
  3         13  
  3         7  
  3         12  
324              
325 3         6 $assertion =~ s/^\+/00/;
326 3         9 $assertion =~ s/\D//g;
327 3 50 33     16 return undef if (!@vals or $assertion =~ /^$/);
328 3 50       5 return (grep { $assertion eq $_ } @vals) ? 1 : 0;
  3         30  
329             }
330              
331             sub _cis_orderingMatch($$@)
332             {
333 4     4   5 my $assertion = shift;
334 4         7 my $op = shift;
335              
336 4 100       11 if ($op eq 'greaterOrEqual') {
    50          
337 2 50       4 return (grep { lc($_) ge lc($assertion) } @_) ? 1 : 0;
  2         22  
338             }
339             elsif ($op eq 'lessOrEqual') {
340 2 50       4 return (grep { lc($_) le lc($assertion) } @_) ? 1 : 0;
  2         22  
341             }
342             else {
343 0         0 return undef; #something went wrong
344             };
345             }
346              
347             sub _exact_orderingMatch($$@)
348             {
349 4     4   9 my $assertion = shift;
350 4         5 my $op = shift;
351              
352 4 100       14 if ($op eq 'greaterOrEqual') {
    50          
353 2 50       4 return (grep { $_ ge $assertion } @_) ? 1 : 0;
  2         30  
354             }
355             elsif ($op eq 'lessOrEqual') {
356 2 50       5 return (grep { $_ le $assertion } @_) ? 1 : 0;
  2         21  
357             }
358             else {
359 0         0 return undef; #something went wrong
360             };
361             }
362              
363             sub _numeric_orderingMatch($$@)
364             {
365 5     5   9 my $assertion = shift;
366 5         7 my $op = shift;
367              
368 5 100       12 if ($op eq 'greaterOrEqual') {
    50          
369 3 50       6 return (grep { $_ >= $assertion } @_) ? 1 : 0;
  3         31  
370             }
371             elsif ($op eq 'lessOrEqual') {
372 2 50       4 return (grep { $_ <= $assertion } @_) ? 1 : 0;
  2         24  
373             }
374             else {
375 0         0 return undef; #something went wrong
376             };
377             }
378              
379             sub _cis_substrings($$@)
380             {
381 18     18   33 my $regex=shift;
382 18         20 my $op=shift;
383              
384 18 50       42 return 1 if ($regex =~ /^$/);
385 18 100       310 return grep(/$regex/i, @_) ? 1 : 0;
386             }
387              
388             sub _exact_substrings($$@)
389             {
390 0     0   0 my $regex=shift;
391 0         0 my $op=shift;
392              
393 0 0       0 return 1 if ($regex =~ /^$/);
394 0 0       0 return grep(/$regex/, @_) ? 1 : 0;
395             }
396              
397             sub _tel_substrings($$@)
398             {
399 4     4   6 my $regex = shift;
400 4         7 my $op = shift;
401 4         7 my @vals = map { s/\+/00/; s/\D//g; $_ } grep { /^\+?[\d\s-]+$/ } @_;
  4         10  
  4         17  
  4         9  
  4         16  
402              
403 4         9 $regex =~ s/\\\+/00/;
404 4         9 $regex =~ s/\\.//g;
405 4         7 $regex =~ s/[^\d\.\*\$\^]//g;
406 4 50 33     19 return undef if (!@vals or $regex =~ /^$/);
407 4 50       80 return grep(/$regex/, @vals) ? 1 : 0;
408             }
409              
410             # this one is here in case we don't use schema
411              
412             sub _cis_greaterOrEqual($$@)
413             {
414 7     7   12 my $assertion=shift;
415 7         12 my $op=shift;
416              
417 7 100       38 if (grep(!/^-?\d+$/o, $assertion, @_)) { # numerical values only => compare numerically
418 4         13 return _cis_orderingMatch($assertion, $op, @_);
419             }
420             else {
421 3         8 return _numeric_orderingMatch($assertion, $op, @_);
422             }
423             }
424              
425             *_cis_lessOrEqual = \&_cis_greaterOrEqual;
426              
427             sub _cis_approxMatch($$@)
428             {
429 20     20   41 my $assertion = lc(+shift);
430 20         24 my $op = shift;
431 20         69 my @vals = map(lc, @_);
432              
433 20         34 foreach (@approxMatchers) {
434             # print "using $_\n";
435 20 50       78 if (/String::Approx/){
    50          
    50          
436 0 0       0 return String::Approx::amatch($assertion, @vals) ? 1 : 0;
437             }
438             elsif (/Text::Metaphone/){
439 0         0 my $metamatch = Text::Metaphone::Metaphone($assertion);
440 0 0       0 return grep((Text::Metaphone::Metaphone($_) eq $metamatch), @vals) ? 1 : 0;
441             }
442             elsif (/Text::Soundex/){
443 20         53 my $smatch = Text::Soundex::soundex($assertion);
444 20 50       267 return grep((Text::Soundex::soundex($_) eq $smatch), @vals) ? 1 : 0;
445             }
446             }
447             # we really have nothing, use plain regexp
448 0 0         return 1 if ($assertion =~ /^$/);
449 0 0         return grep(/^$assertion$/i, @vals) ? 1 : 0;
450             }
451              
452             1;
453              
454              
455             __END__