File Coverage

blib/lib/Test/Net/LDAP/Mock/Data.pm
Criterion Covered Total %
statement 312 329 94.8
branch 137 170 80.5
condition 43 61 70.4
subroutine 33 33 100.0
pod 0 17 0.0
total 525 610 86.0


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