File Coverage

blib/lib/Net/LDAP/Schema.pm
Criterion Covered Total %
statement 126 180 70.0
branch 54 96 56.2
condition 10 17 58.8
subroutine 20 33 60.6
pod 24 27 88.8
total 234 353 66.2


line stmt bran cond sub pod time code
1             # Copyright (c) 1998-2004 Graham Barr . All rights reserved.
2             # This program is free software; you can redistribute it and/or
3             # modify it under the same terms as Perl itself.
4              
5             package Net::LDAP::Schema;
6              
7 2     2   72076 use strict;
  2         13  
  2         5041  
8              
9             our $VERSION = '0.9908';
10              
11             #
12             # Get schema from the server (or read from LDIF) and parse it into
13             # data structure
14             #
15             sub new {
16 2     2 0 594 my $self = shift;
17 2   33     13 my $type = ref($self) || $self;
18 2         7 my $schema = bless {}, $type;
19              
20 2 100       10 @_ ? $schema->parse(@_) : $schema;
21             }
22              
23             sub _error {
24 0     0   0 my $self = shift;
25 0         0 $self->{error} = shift;
26 0         0 return;
27             }
28              
29              
30             sub parse {
31 2     2 1 370 my $schema = shift;
32 2         16 my $arg = shift;
33              
34 2 50       11 unless (defined($arg)) {
35 0         0 $schema->_error('Bad argument');
36 0         0 return undef;
37             }
38              
39 2         12 %$schema = ();
40              
41 2         6 my $entry;
42 2 50       49 if ( ref $arg ) {
    50          
43 0 0       0 if (eval { $arg->isa('Net::LDAP::Entry') }) {
  0 0       0  
44 0         0 $entry = $arg;
45             }
46 0         0 elsif (eval { $arg->isa('Net::LDAP::Search') }) {
47 0 0       0 unless ($entry = $arg->entry) {
48 0         0 $schema->_error('Bad Argument');
49 0         0 return undef;
50             }
51             }
52             else {
53 0         0 $schema->_error('Bad Argument');
54 0         0 return undef;
55             }
56             }
57             elsif ( -f $arg ) {
58 2         1111 require Net::LDAP::LDIF;
59 2         22 my $ldif = Net::LDAP::LDIF->new( $arg, 'r' );
60 2         13 $entry = $ldif->read();
61 2 50       25 unless ( $entry ) {
62 0         0 $schema->_error("Cannot parse LDIF from file [$arg]");
63 0         0 return undef;
64             }
65             }
66             else {
67 0         0 $schema->_error("Can't load schema from [$arg]: $!");
68 0         0 return undef;
69             }
70              
71 2         6 eval {
72 2     0   25 local $SIG{__DIE__} = sub {};
73 2         11 _parse_schema( $schema, $entry );
74             };
75              
76 2 50       17 if ($@) {
77 0         0 $schema->_error($@);
78 0         0 return undef;
79             }
80              
81 2         28 return $schema;
82             }
83              
84             #
85             # Dump as LDIF
86             #
87             # XXX - We should really dump from the internal structure. That way we can
88             # have methods to modify the schema and write a new one -- GMB
89             sub dump {
90 0     0 1 0 my $self = shift;
91 0 0       0 my $fh = @_ ? shift : \*STDOUT;
92 0 0       0 my $entry = $self->{entry} or return;
93 0         0 require Net::LDAP::LDIF;
94 0         0 Net::LDAP::LDIF->new($fh, 'w', wrap => 0)->write($entry);
95 0         0 1;
96             }
97              
98             #
99             # Given another Net::LDAP::Schema, merge the contents together.
100             # XXX - todo
101             #
102             sub merge {
103 0     0 0 0 my $self = shift;
104 0         0 my $new = shift;
105              
106             # Go through structure of 'new', copying code to $self. Take some
107             # parameters describing what to do in the event of a clash.
108             }
109              
110              
111 2     2 1 1249 sub all_attributes { values %{shift->{at}} }
  2         91  
112 1     1 1 697 sub all_objectclasses { values %{shift->{oc}} }
  1         22  
113 2     2 1 1072 sub all_syntaxes { values %{shift->{syn}} }
  2         20  
114 1     1 1 568 sub all_matchingrules { values %{shift->{mr}} }
  1         11  
115 1     1 1 598 sub all_matchingruleuses { values %{shift->{mru}} }
  1         10  
116 0     0 1 0 sub all_ditstructurerules { values %{shift->{dts}} }
  0         0  
117 0     0 1 0 sub all_ditcontentrules { values %{shift->{dtc}} }
  0         0  
118 0     0 1 0 sub all_nameforms { values %{shift->{nfm}} }
  0         0  
119              
120             sub superclass {
121 1     1 1 313 my $self = shift;
122 1         4 my $oc = shift;
123              
124 1 50       10 my $elem = $self->objectclass( $oc )
125             or return scalar _error($self, 'Not an objectClass');
126              
127 1 50       3 return @{$elem->{sup} || []};
  1         8  
128             }
129              
130 1     1 1 1258 sub must { _must_or_may(@_, 'must') }
131 1     1 1 613 sub may { _must_or_may(@_, 'may') }
132              
133             #
134             # Return must or may attributes for this OC.
135             #
136             sub _must_or_may {
137 2     2   7 my $self = shift;
138 2         3 my $must_or_may = pop;
139 2 50       10 my @oc = @_ or return;
140              
141             #
142             # If called with an entry, get the OC names and continue
143             #
144 2 50       4 if (eval { $oc[0]->isa('Net::LDAP::Entry') }) {
  2         20  
145 0         0 my $entry = $oc[0];
146 0 0       0 @oc = $entry->get_value( 'objectclass' )
147             or return;
148             }
149              
150 2         5 my %res;
151             my %done;
152              
153 2         6 while (@oc) {
154 3         6 my $oc = shift @oc;
155              
156 3 50       13 $done{lc $oc}++ and next;
157              
158 3 100       10 my $elem = $self->objectclass( $oc ) or next;
159 2 50       8 if (my $res = $elem->{$must_or_may}) {
160 2         10 @res{ @$res } = (); # Add in, getting uniqueness
161             }
162 2 100       8 my $sup = $elem->{sup} or next;
163 1         3 push @oc, @$sup;
164             }
165              
166 2         10 my %unique = map { ($_, $_) } $self->attribute(keys %res);
  3         9  
167 2         16 values %unique;
168             }
169              
170             #
171             # Given name or oid, return element or undef if not of appropriate type
172             #
173              
174             sub _get {
175 55     55   72 my $self = shift;
176 55         87 my $type = pop(@_);
177 55         93 my $hash = $self->{$type};
178 55         81 my $oid = $self->{oid};
179              
180             my @elem = grep $_, map {
181 55         98 my $elem = $hash->{lc $_};
  56         127  
182              
183 56 100 66     242 ($elem or ($elem = $oid->{$_} and $elem->{type} eq $type))
184             ? $elem
185             : undef;
186             } @_;
187              
188 55 100       162 wantarray ? @elem : $elem[0];
189             }
190              
191 44     44 1 637 sub attribute { _get(@_, 'at') }
192 4     4 1 11 sub objectclass { _get(@_, 'oc') }
193 0     0 1 0 sub syntax { _get(@_, 'syn') }
194 4     4 1 12 sub matchingrule { _get(@_, 'mr') }
195 3     3 1 620 sub matchingruleuse { _get(@_, 'mru') }
196 0     0 1 0 sub ditstructurerule { _get(@_, 'dts') }
197 0     0 1 0 sub ditcontentrule { _get(@_, 'dtc') }
198 0     0 1 0 sub nameform { _get(@_, 'nfm') }
199              
200              
201             #
202             # XXX - TODO - move long comments to POD and write up interface
203             #
204             # Data structure is:
205             #
206             # $schema (hash ref)
207             #
208             # The {oid} piece here is a little redundant since we control the other
209             # top-level members. We promote the first listed name to be 'canonical' and
210             # also make up a name for syntaxes (from the description). Thus we always
211             # have a unique name. This avoids a lot of checking in the access routines.
212             #
213             # ->{oid}->{$oid}->{
214             # name => $canonical_name, (created for syn)
215             # aliases => list of non. canon names
216             # type => at/oc/syn
217             # desc => description
218             # must => list of can. names of mand. atts [if OC]
219             # may => list of can. names of opt. atts [if OC]
220             # syntax => can. name of syntax [if AT]
221             # ... etc per oid details
222             #
223             # These next items are optimisations, to avoid always searching the OID
224             # lists. Could be removed in theory. Each is a hash ref mapping
225             # lowercase names to the hash stored in the oid structure
226             #
227             # ->{at}
228             # ->{oc}
229             # ->{syn}
230             # ->{mr}
231             # ->{mru}
232             # ->{dts}
233             # ->{dtc}
234             # ->{nfm}
235             #
236              
237             #
238             # These items have no following arguments
239             #
240             my %flags = map { ($_, 1) } qw(
241             single-value
242             obsolete
243             collective
244             no-user-modification
245             abstract
246             structural
247             auxiliary
248             );
249              
250             my %xat_flags = map { ($_, 1) } qw(indexed system-only);
251              
252             #
253             # These items can have lists arguments
254             # (name can too, but we treat it special)
255             #
256             my %listops = map { ($_, 1) } qw(must may sup);
257              
258             #
259             # Map schema attribute names to internal names
260             #
261             my %type2attr = qw(
262             at attributetypes
263             xat extendedAttributeInfo
264             oc objectclasses
265             syn ldapsyntaxes
266             mr matchingrules
267             mru matchingruleuse
268             dts ditstructurerules
269             dtc ditcontentrules
270             nfm nameforms
271             );
272              
273             #
274             # Return ref to hash containing schema data - undef on failure
275             #
276              
277             sub _parse_schema {
278 2     2   6 my $schema = shift;
279 2         4 my $entry = shift;
280              
281 2 50       7 return undef unless defined($entry);
282              
283 2         9 keys %type2attr; # reset iterator
284 2         15 while (my($type, $attr) = each %type2attr) {
285 18         71 my $vals = $entry->get_value($attr, asref => 1);
286              
287 18         28 my %names;
288 18         68 $schema->{$type} = \%names; # Save reference to hash of names => element
289              
290 18 100       71 next unless $vals; # Just leave empty ref if nothing
291              
292 10         33 foreach my $val (@$vals) {
293             #
294             # The following statement takes care of defined attributes
295             # that have no data associated with them.
296             #
297 828 50       1741 next if $val eq '';
298              
299             #
300             # We assume that each value can be turned into an OID, a canonical
301             # name and a 'schema_entry' which is a hash ref containing the items
302             # present in the value.
303             #
304 828         2407 my %schema_entry = ( type => $type, aliases => [] );
305              
306 828         1139 my @tokens;
307 828         1806 pos($val) = 0;
308              
309 828         27728 push @tokens, $+
310             while $val =~ /\G\s*(?:
311             ([()])
312             |
313             ([^"'\s()]+)
314             |
315             "([^"]*)"
316             |
317             '((?:[^']+|'[^\s)])*)'
318             )\s*/xcg;
319 828 50 33     3059 die "Cannot parse [$val] [", substr($val, pos($val)), "]"
320             unless @tokens and pos($val) == length($val);
321              
322             # remove () from start/end
323 828 50       1753 shift @tokens if $tokens[0] eq '(';
324 828 50       1587 pop @tokens if $tokens[-1] eq ')';
325              
326             # The first token is the OID
327 828         1626 my $oid = $schema_entry{oid} = shift @tokens;
328              
329 828 50       1565 my $flags = ($type eq 'xat') ? \%xat_flags : \%flags;
330 828         1492 while (@tokens) {
331 2998         4589 my $tag = lc shift @tokens;
332              
333 2998 100       5787 if (exists $flags->{$tag}) {
    50          
334 350         761 $schema_entry{$tag} = 1;
335             }
336             elsif (@tokens) {
337 2648 100       5681 if (($schema_entry{$tag} = shift @tokens) eq '(') {
338 192         257 my @arr;
339 192         289 $schema_entry{$tag} = \@arr;
340 192         265 while (1) {
341 4790         5987 my $tmp = shift @tokens;
342 4790 100       7605 last if $tmp eq ')';
343 4598 100       7743 push @arr, $tmp unless $tmp eq '$';
344              
345             # Drop of end of list ?
346 4598 50       7465 die "Cannot parse [$val] {$tag}" unless @tokens;
347             }
348             }
349              
350             # Ensure items that can be lists are stored as array refs
351             $schema_entry{$tag} = [ $schema_entry{$tag} ]
352 2648 100 100     6895 if exists $listops{$tag} and !ref $schema_entry{$tag};
353             }
354             else {
355 0         0 die "Cannot parse [$val] {$tag}";
356             }
357             }
358              
359             #
360             # Extract the maximum length of a syntax
361             #
362             $schema_entry{max_length} = $1
363 828 100 100     2692 if exists $schema_entry{syntax} and $schema_entry{syntax} =~ s/{(\d+)}//;
364              
365             #
366             # Force a name if we don't have one
367             #
368             $schema_entry{name} = $schema_entry{oid}
369 828 100       1534 unless exists $schema_entry{name};
370              
371             #
372             # If we have multiple names, make the name be the first and demote the rest to aliases
373             #
374 828 100       1349 if (ref $schema_entry{name}) {
375 44         62 my $aliases;
376 44         57 $schema_entry{name} = shift @{$aliases = $schema_entry{name}};
  44         88  
377 44 50       100 $schema_entry{aliases} = $aliases if @$aliases;
378             }
379              
380             #
381             # Store the elements by OID
382             #
383 828 50       2426 $schema->{oid}->{$oid} = \%schema_entry unless $type eq 'xat';
384              
385             #
386             # We also index elements by name within each type
387             #
388 828         1084 foreach my $name ( @{$schema_entry{aliases}}, $schema_entry{name} ) {
  828         1617  
389 874         1323 my $lc_name = lc $name;
390 874         2899 $names{lc $name} = \%schema_entry;
391             }
392             }
393             }
394              
395             # place extendedAttributeInfo into attribute types
396 2 50       14 if (my $xat = $schema->{xat}) {
397 2         11 foreach my $xat_ref (values %$xat) {
398 0   0     0 my $oid = $schema->{oid}{$xat_ref->{oid}} ||= {};
399 0         0 while (my($k, $v) = each %$xat_ref) {
400 0 0       0 $oid->{"x-$k"} = $v unless $k =~ /^(oid|type|name|aliases)$/;
401             }
402             }
403             }
404              
405 2         7 $schema->{entry} = $entry;
406 2         43 return $schema;
407             }
408              
409              
410              
411              
412             #
413             # Get the syntax of an attribute
414             #
415             sub attribute_syntax {
416 0     0 1 0 my $self = shift;
417 0         0 my $attr = shift;
418 0         0 my $syntax;
419              
420 0         0 while ($attr) {
421 0 0       0 my $elem = $self->attribute( $attr ) or return undef;
422              
423 0 0       0 $syntax = $elem->{syntax} and return $self->syntax($syntax);
424              
425 0 0       0 $attr = ${$elem->{sup} || []}[0];
  0         0  
426             }
427              
428 0         0 return undef;
429             }
430              
431              
432             sub error {
433 1     1 1 7 $_[0]->{error};
434             }
435              
436             #
437             # Return base entry
438             #
439             sub entry {
440 0     0 0 0 $_[0]->{entry};
441             }
442              
443             sub matchingrule_for_attribute {
444 40     40 1 65 my $self = shift;
445 40         57 my $attr = shift;
446 40         59 my $matchtype = shift;
447              
448 40         110 my $attrtype = $self->attribute( $attr );
449 40 100       120 if (exists $attrtype->{$matchtype}) {
    100          
450 30         85 return $attrtype->{$matchtype};
451             } elsif (exists $attrtype->{sup}) {
452             # the assumption is that all superiors result in the same ruleset
453             return $self->matchingrule_for_attribute(
454 8         24 $attrtype->{sup}[0],
455             $matchtype);
456             }
457 2         6 return undef;
458             }
459              
460             1;