File Coverage

blib/lib/Test/Net/LDAP/Mock/Data.pm
Criterion Covered Total %
statement 313 330 94.8
branch 139 172 80.8
condition 41 59 69.4
subroutine 33 33 100.0
pod 0 17 0.0
total 526 611 86.0


line stmt bran cond sub pod time code
1 13     13   478534 use 5.006;
  13         32  
  13         382  
2 13     13   48 use strict;
  13         16  
  13         327  
3 13     13   44 use warnings;
  13         18  
  13         518  
4              
5             package Test::Net::LDAP::Mock::Data;
6 13     13   52 use base qw(Test::Net::LDAP::Mixin);
  13         28  
  13         2874  
7              
8 13     13   59 use Net::LDAP;
  13         13  
  13         72  
9 13         864 use Net::LDAP::Constant qw(
10             LDAP_SUCCESS
11             LDAP_COMPARE_TRUE LDAP_COMPARE_FALSE
12             LDAP_NO_SUCH_OBJECT LDAP_ALREADY_EXISTS
13             LDAP_INVALID_DN_SYNTAX LDAP_PARAM_ERROR
14             LDAP_INVALID_CREDENTIALS LDAP_INAPPROPRIATE_AUTH
15 13     13   690 );
  13         16  
16 13     13   3775 use Net::LDAP::Entry;
  13         16728  
  13         287  
17 13     13   5586 use Net::LDAP::Filter;
  13         22972  
  13         346  
18 13     13   5623 use Net::LDAP::FilterMatch;
  13         51249  
  13         92  
19 13         761 use Net::LDAP::Util qw(
20             canonical_dn escape_dn_value ldap_explode_dn
21 13     13   36877 );
  13         20  
22 13     13   63 use Scalar::Util qw(blessed);
  13         19  
  13         877  
23 13     13   187 use Test::Net::LDAP::Util;
  13         19  
  13         30109  
24              
25             my %scope = qw(base 0 one 1 single 1 sub 2 subtree 2);
26             my %deref = qw(never 0 search 1 find 2 always 3);
27             %scope = (%scope, map {$_ => $_} values %scope);
28             %deref = (%deref, map {$_ => $_} values %deref);
29              
30             sub new {
31 30     30 0 170 my ($class, $ldap) = @_;
32 30         6126 require Test::Net::LDAP::Mock::Node;
33            
34 30         163 my $self = bless {
35             root => Test::Net::LDAP::Mock::Node->new,
36             ldap => $ldap,
37             schema => undef,
38             bind_success => 0,
39             password_mocked => 0,
40             mock_bind_code => LDAP_SUCCESS,
41             mock_bind_message => '',
42             }, $class;
43            
44 30   66     314 $self->{ldap} ||= do {
45 8         3491 require Test::Net::LDAP::Mock;
46 8         55 my $ldap = Test::Net::LDAP::Mock->new;
47 8         14 $ldap->{mock_data} = $self;
48 8         13 $ldap;
49             };
50            
51 30         106 return $self;
52             }
53              
54             sub root {
55 136     136 0 454 shift->{root};
56             }
57              
58             sub schema {
59 200     200 0 159 my $self = shift;
60            
61 200 50       248 if (@_) {
62 0         0 my $schema = $self->{schema};
63 0         0 $self->{schema} = $_[0];
64 0         0 return $schema;
65             } else {
66 200         247 return $self->{schema};
67             }
68             }
69              
70             sub ldap {
71 218     218 0 197 my $self = shift;
72            
73 218 50       331 if (@_) {
74 0         0 my $ldap = $self->{ldap};
75 0         0 $self->{ldap} = $_[0];
76 0         0 return $ldap;
77             } else {
78 218         775 return $self->{ldap};
79             }
80             }
81              
82             sub root_dse {
83 1     1 0 7 my $self = shift;
84 1         4 $self->ldap->root_dse(@_);
85             }
86              
87             sub mock_root_dse {
88 1     1 0 6 my $self = shift;
89 1         3 my $root_node = $self->root;
90            
91 1 50       4 if (@_) {
92 1         412 require Net::LDAP::RootDSE;
93 1         195 my $old_entry = $root_node->entry;
94 1         1 my $new_entry;
95            
96 1 50 33     13 if ($_[0] && blessed($_[0]) && $_[0]->isa('Net::LDAP::Entry')) {
      33        
97 0         0 $new_entry = $_[0]->clone;
98 0         0 $new_entry->dn('');
99            
100 0 0       0 unless ($new_entry->isa('Net::LDAP::RootDSE')) {
101 0         0 bless $new_entry, 'Net::LDAP::RootDSE';
102             }
103             } else {
104 1         8 $new_entry = Net::LDAP::RootDSE->new('', @_);
105             }
106            
107 1 50       70 unless ($new_entry->get_value('objectClass')) {
108 1         13 $new_entry->add(objectClass => 'top');
109             # Net::LDAP::root_dse uses the filter '(objectclass=*)' to search
110             # for the root DSE.
111             }
112            
113 1         17 $root_node->entry($new_entry);
114 1         2 return $old_entry;
115             } else {
116 0         0 return $root_node->entry;
117             }
118             }
119              
120             sub mock_bind {
121 20     20 0 5205 my $self = shift;
122 20         42 my @values = ($self->{mock_bind_code}, $self->{mock_bind_message});
123            
124 20 100       37 if (@_) {
125 18         21 $self->{mock_bind_code} = shift;
126 18         20 $self->{mock_bind_message} = shift;
127             }
128            
129 20 50       58 return wantarray ? @values : $values[0];
130             }
131              
132             sub mock_password {
133 6     6 0 11 my $self = shift;
134 6 50       11 my $dn = shift or return;
135            
136 6 100       12 if (@_) {
137 2         3 my $password = shift;
138 2         3 $self->{password_mocked} = 1;
139 2         4 my $node = $self->root->make_node($dn);
140 2         9 return $node->password($password);
141             } else {
142 4 100       7 my $node = $self->root->get_node($dn) or return;
143 2         7 return $node->password();
144             }
145             }
146              
147             sub _result_entry {
148 75     75   86 my ($self, $input_entry, $arg) = @_;
149 75   100     209 my $attrs = $arg->{attrs} || [];
150 75         66 my $output_entry;
151            
152 75 100       116 if (@$attrs) {
153 29         106 $output_entry = Net::LDAP::Entry->new;
154 29         328 $output_entry->dn($input_entry->dn);
155            
156 69         426 $output_entry->add(
157 29         218 map {$_ => [$input_entry->get_value($_)]} @$attrs
158             );
159             } else {
160 46         94 $output_entry = $input_entry->clone;
161             }
162            
163 75         4739 $output_entry->changetype('modify');
164 75         402 return $output_entry;
165             }
166              
167             sub _error {
168 44     44   55 my $self = shift;
169 44         73 $self->ldap->_error(@_);
170             }
171              
172             sub _mock_message {
173 173     173   187 my $self = shift;
174 173         344 $self->ldap->_mock_message(@_);
175             }
176              
177             sub bind {
178 30     30 0 46 my $self = shift;
179 30         59 my $arg = &Net::LDAP::_dn_options;
180 30         1350 require Net::LDAP::Bind;
181 30         743 my $mesg = $self->_mock_message('Net::LDAP::Bind' => $arg);
182            
183 30 100 100     79 if ($self->{password_mocked} && exists $arg->{password}) {
184 4         5 my $dn = $arg->{dn};
185            
186 4 100       6 if (!defined $dn) {
187 1         4 return $self->_error($mesg, LDAP_INAPPROPRIATE_AUTH, 'No password, did you mean noauth or anonymous ?');
188             }
189            
190 3 50       8 $dn = ldap_explode_dn($dn, casefold => 'lower')
191             or return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
192            
193 3 100       241 my $node = $self->root->get_node($dn)
194             or return $self->_error($mesg, LDAP_NO_SUCH_OBJECT, '');
195            
196 2 100 33     7 unless (defined $node->password && defined $arg->{password}
      66        
197             && $node->password eq $arg->{password}) {
198 1         3 return $self->_error($mesg, LDAP_INVALID_CREDENTIALS, '');
199             }
200             }
201            
202 27 100       53 if (my $code = $self->{mock_bind_code}) {
203 9   100     21 my $message = $self->{mock_bind_message} || '';
204            
205 9 100       21 if (ref $code eq 'CODE') {
206             # Callback
207 3         7 my @result = $code->($arg);
208 3   100     1738 ($code, $message) = ($result[0] || LDAP_SUCCESS, $result[1] || $message);
      66        
209             }
210            
211 9 100       29 if (blessed $code) {
212             # Assume $code is a LDAP::Message
213 4   66     9 ($code, $message) = ($code->code, $message || $code->error);
214             }
215            
216 9 100       68 if ($code != LDAP_SUCCESS) {
217 8         12 return $self->_error($mesg, $code, $message);
218             }
219             }
220            
221 19 100       36 if (my $callback = $arg->{callback}) {
222 1         3 $callback->($mesg);
223             }
224            
225 19         50 return $mesg;
226             }
227              
228             sub unbind {
229 2     2 0 3 my $self = shift;
230 2         6 my $arg = &Net::LDAP::_dn_options;
231 2         23 my $mesg = $self->_mock_message('Net::LDAP::Unbind' => $arg);
232            
233 2 100       6 if (my $callback = $arg->{callback}) {
234 1         2 $callback->($mesg);
235             }
236            
237 2         8 return $mesg;
238             }
239              
240             sub abandon {
241 2     2 0 3 my $self = shift;
242 2         7 my $arg = &Net::LDAP::_dn_options;
243 2         23 my $mesg = $self->_mock_message('Net::LDAP::Abandon' => $arg);
244            
245 2 100       7 if (my $callback = $arg->{callback}) {
246 1         4 $callback->($mesg);
247             }
248            
249 2         9 return $mesg;
250             }
251              
252             sub search {
253 52     52 0 93 my $self = shift;
254 52         116 my $arg = &Net::LDAP::_dn_options;
255            
256 52         5082 require Net::LDAP::Search;
257 52         14571 my $mesg = $self->_mock_message('Net::LDAP::Search' => $arg);
258            
259             # Configure params
260 52   100     168 my $base = $arg->{base} || '';
261 52         129 $base = ldap_explode_dn($base, casefold => 'lower');
262            
263 52 100       2394 unless ($base) {
264 1         4 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
265             }
266            
267 51         77 my $filter = $arg->{filter};
268            
269 51 100 66     334 if (defined $filter && !ref($filter) && $filter ne '') {
      100        
270 39         164 my $f = Net::LDAP::Filter->new;
271            
272 39 100       356 unless ($f->parse($filter)) {
273 1         33 return $self->_error($mesg, LDAP_PARAM_ERROR, 'Bad filter');
274             }
275            
276 38         2320 $filter = $f;
277             } else {
278 12         11 $filter = undef;
279             }
280            
281 50 100       131 my $scope = defined $arg->{scope} ? $arg->{scope} : 'sub';
282 50         86 $scope = $scope{$scope};
283            
284 50 100       90 unless (defined $scope) {
285 2         3 return $self->_error($mesg, LDAP_PARAM_ERROR, 'invalid scope');
286             }
287            
288 48         51 my $callback = $arg->{callback};
289            
290             # Traverse tree
291 48         78 $mesg->{entries} = [];
292 48 50       138 my $base_node = $base ? $self->root->get_node($base) : $self->root;
293            
294 48 100       179 unless ($base_node) {
295 3         11 return $self->_error($mesg, LDAP_NO_SUCH_OBJECT, '');
296             }
297            
298 45 100       80 $callback->($mesg) if $callback;
299            
300             $base_node->traverse(sub {
301 195     195   143 my ($node) = @_;
302 195         328 my $entry = $node->entry;
303 195         257 my $schema = $self->schema;
304            
305 195 100 100     691 if ($entry && (!$filter || $filter->match($entry, $schema))) {
      66        
306 75         3664 my $result_entry = $self->_result_entry($entry, $arg);
307 75         67 push @{$mesg->{entries}}, $result_entry;
  75         110  
308 75 100       202 $callback->($mesg, $result_entry) if $callback;
309             }
310 45         239 }, $scope);
311            
312 45         215 return $mesg;
313             }
314              
315             sub compare {
316 8     8 0 9 my $self = shift;
317 8         16 my $arg = &Net::LDAP::_dn_options;
318 8         127 my $mesg = $self->_mock_message('Net::LDAP::Compare' => $arg);
319            
320 8 50       17 my $dn = (ref $arg->{dn} ? $arg->{dn}->dn : $arg->{dn});
321            
322 8 100       14 unless ($dn) {
323 1         3 return $self->_error($mesg, LDAP_PARAM_ERROR, 'No DN specified');
324             }
325            
326 7         14 my $dn_list = ldap_explode_dn($dn, casefold => 'lower');
327            
328 7 100       446 unless ($dn_list) {
329 2         5 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
330             }
331            
332 5 0       13 my $attr = exists $arg->{attr}
    50          
333             ? $arg->{attr}
334             : exists $arg->{attrs} #compat
335             ? $arg->{attrs}[0]
336             : "";
337              
338 5 0       12 my $value = exists $arg->{value}
    50          
339             ? $arg->{value}
340             : exists $arg->{attrs} #compat
341             ? $arg->{attrs}[1]
342             : "";
343            
344 5         10 my $node = $self->root->get_node($dn_list);
345            
346 5 50 33     24 unless ($node && $node->entry) {
347 0         0 return $self->_error($mesg, LDAP_NO_SUCH_OBJECT, '');
348             }
349            
350 5         11 my $entry = $node->entry;
351            
352 5         21 my $filter = bless {
353             equalityMatch => {
354             attributeDesc => $attr,
355             assertionValue => $value,
356             }
357             }, 'Net::LDAP::Filter';
358            
359 5 100       17 $mesg->{resultCode} = $filter->match($entry, $self->schema)
360             ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
361            
362 5 100       553 if (my $callback = $arg->{callback}) {
363 1         3 $callback->($mesg);
364             }
365            
366 5         37 return $mesg;
367             }
368              
369             sub add {
370 40     40 0 53 my $self = shift;
371 40         91 my $arg = &Net::LDAP::_dn_options;
372 40         618 my $mesg = $self->_mock_message('Net::LDAP::Add' => $arg);
373            
374 40 50       98 my $dn = ref $arg->{dn} ? $arg->{dn}->dn : $arg->{dn};
375            
376 40 100       90 unless ($dn) {
377 1         6 return $self->_error($mesg, LDAP_PARAM_ERROR, 'No DN specified');
378             }
379            
380 39         100 my $dn_list = ldap_explode_dn($dn, casefold => 'lower');
381            
382 39 100       4158 unless ($dn_list) {
383 2         7 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
384             }
385            
386 37         88 my $node = $self->root->make_node($dn);
387            
388 37 100       156 if ($node->entry) {
389 2         11 return $self->_error($mesg, LDAP_ALREADY_EXISTS, '');
390             }
391            
392 35         31 my $entry;
393            
394 35 50       73 if (ref $arg->{dn}) {
395 0         0 $entry = $arg->{dn}->clone;
396             } else {
397 35 100 66     268 $entry = Net::LDAP::Entry->new(
398             $arg->{dn},
399 35         45 @{$arg->{attrs} || $arg->{attr} || []}
400             );
401             }
402            
403 35 50       879 if (my $rdn = $dn_list->[0]) {
404 35         120 $entry->delete(%$rdn);
405 35         982 $entry->add(%$rdn);
406             }
407            
408 35         537 $entry->changetype('add');
409 35         224 $node->entry($entry);
410            
411 35 100       85 if (my $callback = $arg->{callback}) {
412 1         4 $callback->($mesg);
413             }
414            
415 35         179 return $mesg;
416             }
417              
418             my %opcode = (add => 0, delete => 1, replace => 2, increment => 3);
419              
420             sub modify {
421 19     19 0 19 my $self = shift;
422 19         44 my $arg = &Net::LDAP::_dn_options;
423 19         308 my $mesg = $self->_mock_message('Net::LDAP::Modify' => $arg);
424            
425 19 50       46 my $dn = (ref $arg->{dn} ? $arg->{dn}->dn : $arg->{dn});
426            
427 19 100       67 unless ($dn) {
428 1         6 return $self->_error($mesg, LDAP_PARAM_ERROR, 'No DN specified');
429             }
430            
431 18         52 my $dn_list = ldap_explode_dn($dn, casefold => 'lower');
432            
433 18 100       1321 unless ($dn_list) {
434 2         4 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
435             }
436            
437 16         41 my $node = $self->root->get_node($dn_list);
438            
439 16 100 66     87 unless ($node && $node->entry) {
440 2         7 return $self->_error($mesg, LDAP_NO_SUCH_OBJECT, '');
441             }
442            
443 14         30 my $entry = $node->entry;
444            
445 14 100       29 if (exists $arg->{changes}) {
446 2         4 for (my $j = 0; $j < @{$arg->{changes}}; $j += 2) {
  5         89  
447 4         6 my $op = $arg->{changes}[$j];
448 4         5 my $chg = $arg->{changes}[$j + 1];
449            
450 4 100       8 unless (defined $opcode{$op}) {
451 1         5 return $self->_error($mesg, LDAP_PARAM_ERROR, "Bad change type '$op'");
452             }
453            
454 3         7 $entry->$op(@$chg);
455             }
456             } else {
457 12         33 for my $op (keys %opcode) {
458 48 100       341 my $chg = $arg->{$op} or next;
459 11         15 my $opcode = $opcode{$op};
460 11         15 my $ref_chg = ref $chg;
461            
462 11 100       49 if ($opcode == 3) {
    100          
    50          
    0          
463             # $op eq 'increment'
464 2 100       6 if ($ref_chg eq 'HASH') {
    50          
    0          
465 1         3 for my $attr (keys %$chg) {
466 2         22 my $incr = $chg->{$attr};
467            
468 3         28 $entry->replace(
469 2         5 $attr => [map {$_ + $incr} $entry->get_value($attr)]
470             );
471             }
472             } elsif ($ref_chg eq 'ARRAY') {
473 1         4 for (my $i = 0; $i < @$chg; $i += 2) {
474 2         16 my ($attr, $incr) = ($chg->[$i], $chg->[$i + 1]);
475 2 50       4 next unless defined $incr;
476            
477 3         19 $entry->replace(
478 2         5 $attr => [map {$_ + $incr} $entry->get_value($attr)]
479             );
480             }
481             } elsif (!$ref_chg) {
482 0         0 $entry->replace(
483 0         0 $chg => [map {$_ + 1} $entry->get_value($chg)]
484             );
485             }
486             } elsif ($ref_chg eq 'HASH') {
487 5         23 $entry->$op(%$chg);
488             } elsif ($ref_chg eq 'ARRAY') {
489 4 100       13 if ($opcode == 1) {
490             # $op eq 'delete'
491 1         2 $entry->$op(map {$_ => []} @$chg);
  2         7  
492             } else {
493 3         16 $entry->$op(@$chg);
494             }
495             } elsif (!$ref_chg) {
496 0         0 $entry->$op($chg => []);
497             }
498             }
499             }
500            
501 13 100       117 if (my $callback = $arg->{callback}) {
502 2         6 $callback->($mesg);
503             }
504            
505 13         67 return $mesg;
506             }
507              
508             sub delete {
509 8     8 0 9 my $self = shift;
510 8         15 my $arg = &Net::LDAP::_dn_options;
511 8         97 my $mesg = $self->_mock_message('Net::LDAP::Delete' => $arg);
512            
513 8 50       15 my $dn = (ref $arg->{dn} ? $arg->{dn}->dn : $arg->{dn});
514            
515 8 100       13 unless ($dn) {
516 1         3 return $self->_error($mesg, LDAP_PARAM_ERROR, 'No DN specified');
517             }
518            
519 7         16 my $dn_list = ldap_explode_dn($dn, casefold => 'lower');
520            
521 7 100       351 unless ($dn_list) {
522 2         5 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
523             }
524            
525 5         8 my $node = $self->root->get_node($dn_list);
526            
527 5 100 66     21 unless ($node && $node->entry) {
528 1         3 return $self->_error($mesg, LDAP_NO_SUCH_OBJECT, '');
529             }
530            
531 4         6 $node->entry(undef);
532            
533 4 100       9 if (my $callback = $arg->{callback}) {
534 1         3 $callback->($mesg);
535             }
536            
537 4         17 return $mesg;
538             }
539              
540             sub moddn {
541 12     12 0 11 my $self = shift;
542 12         24 my $arg = &Net::LDAP::_dn_options;
543 12         167 my $mesg = $self->_mock_message('Net::LDAP::ModDN' => $arg);
544            
545 12 50       22 my $dn = (ref $arg->{dn} ? $arg->{dn}->dn : $arg->{dn});
546            
547 12 100       22 unless ($dn) {
548 1         4 return $self->_error($mesg, LDAP_PARAM_ERROR, 'No DN specified');
549             }
550            
551 11         22 my $dn_list = ldap_explode_dn($dn, casefold => 'lower');
552            
553 11 100       632 unless ($dn_list) {
554 2         3 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid DN');
555             }
556            
557 9         9 my $old_rdn = $dn_list->[0];
558 9         18 my $old_node = $self->root->get_node($dn_list);
559            
560 9 100 66     40 unless ($old_node && $old_node->entry) {
561 1         3 return $self->_error($mesg, LDAP_NO_SUCH_OBJECT, '');
562             }
563            
564             # Configure new RDN
565 8         9 my $new_rdn;
566 8         8 my $rdn_changed = 0;
567            
568 8 100       15 if (defined(my $new_rdn_value = $arg->{newrdn})) {
569 7         12 my $new_rdn_list = ldap_explode_dn($new_rdn_value, casefold => 'lower');
570            
571 7 100       223 unless ($new_rdn_list) {
572 1         4 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid new RDN');
573             }
574            
575 6         7 $new_rdn = $new_rdn_list->[0];
576 6         12 $rdn_changed = 1;
577             } else {
578 1         3 $new_rdn = $dn_list->[0];
579             }
580            
581             # Configure new DN
582 7 100       13 if (defined(my $new_superior = $arg->{newsuperior})) {
583 4         9 $dn_list = ldap_explode_dn($new_superior, casefold => 'lower');
584            
585 4 100       156 unless ($dn_list) {
586 1         2 return $self->_error($mesg, LDAP_INVALID_DN_SYNTAX, 'invalid newSuperior');
587             }
588            
589 3         6 unshift @$dn_list, $new_rdn;
590             } else {
591 3         4 $dn_list->[0] = $new_rdn;
592             }
593            
594 6         13 my $new_dn = canonical_dn($dn_list, casefold => 'lower');
595            
596             # Create new node
597 6         275 my $new_node = $self->root->make_node($dn_list);
598            
599 6 100       21 if ($new_node->entry) {
600 2         5 return $self->_error($mesg, LDAP_ALREADY_EXISTS, '');
601             }
602            
603             # Set up new entry
604 4         16 my $new_entry = $old_node->entry;
605 4         8 $old_node->entry(undef);
606            
607 4         10 $new_entry->dn($new_dn);
608            
609 4 100       17 if ($rdn_changed) {
610 3 100       6 if ($arg->{deleteoldrdn}) {
611 2         7 $new_entry->delete(%$old_rdn);
612             }
613            
614 3         56 $new_entry->delete(%$new_rdn);
615 3         71 $new_entry->add(%$new_rdn);
616             }
617            
618 4         41 $new_node->entry($new_entry);
619            
620 4 50       10 if (my $callback = $arg->{callback}) {
621 0         0 $callback->($mesg);
622             }
623            
624 4         21 return $mesg;
625             }
626              
627             1;