File Coverage

blib/lib/Test/Net/LDAP/Mock/Data.pm
Criterion Covered Total %
statement 315 332 94.8
branch 141 174 81.0
condition 41 59 69.4
subroutine 33 33 100.0
pod 0 17 0.0
total 530 615 86.1


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