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   57052 use strict;
  2         13  
  2         3754  
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 414 my $self = shift;
17 2   33     10 my $type = ref($self) || $self;
18 2         6 my $schema = bless {}, $type;
19              
20 2 100       9 @_ ? $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 262 my $schema = shift;
32 2         4 my $arg = shift;
33              
34 2 50       6 unless (defined($arg)) {
35 0         0 $schema->_error('Bad argument');
36 0         0 return undef;
37             }
38              
39 2         9 %$schema = ();
40              
41 2         2 my $entry;
42 2 50       43 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         887 require Net::LDAP::LDIF;
59 2         24 my $ldif = Net::LDAP::LDIF->new( $arg, 'r' );
60 2         10 $entry = $ldif->read();
61 2 50       20 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   18 local $SIG{__DIE__} = sub {};
73 2         9 _parse_schema( $schema, $entry );
74             };
75              
76 2 50       8 if ($@) {
77 0         0 $schema->_error($@);
78 0         0 return undef;
79             }
80              
81 2         22 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 1004 sub all_attributes { values %{shift->{at}} }
  2         82  
112 1     1 1 545 sub all_objectclasses { values %{shift->{oc}} }
  1         12  
113 2     2 1 868 sub all_syntaxes { values %{shift->{syn}} }
  2         18  
114 1     1 1 480 sub all_matchingrules { values %{shift->{mr}} }
  1         11  
115 1     1 1 453 sub all_matchingruleuses { values %{shift->{mru}} }
  1         11  
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 257 my $self = shift;
122 1         2 my $oc = shift;
123              
124 1 50       8 my $elem = $self->objectclass( $oc )
125             or return scalar _error($self, 'Not an objectClass');
126              
127 1 50       2 return @{$elem->{sup} || []};
  1         5  
128             }
129              
130 1     1 1 1021 sub must { _must_or_may(@_, 'must') }
131 1     1 1 487 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   4 my $self = shift;
138 2         4 my $must_or_may = pop;
139 2 50       8 my @oc = @_ or return;
140              
141             #
142             # If called with an entry, get the OC names and continue
143             #
144 2 50       3 if (eval { $oc[0]->isa('Net::LDAP::Entry') }) {
  2         14  
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         4 my $oc = shift @oc;
155              
156 3 50       12 $done{lc $oc}++ and next;
157              
158 3 100       7 my $elem = $self->objectclass( $oc ) or next;
159 2 50       5 if (my $res = $elem->{$must_or_may}) {
160 2         6 @res{ @$res } = (); # Add in, getting uniqueness
161             }
162 2 100       6 my $sup = $elem->{sup} or next;
163 1         3 push @oc, @$sup;
164             }
165              
166 2         8 my %unique = map { ($_, $_) } $self->attribute(keys %res);
  3         6  
167 2         13 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   62 my $self = shift;
176 55         73 my $type = pop(@_);
177 55         78 my $hash = $self->{$type};
178 55         61 my $oid = $self->{oid};
179              
180             my @elem = grep $_, map {
181 55         82 my $elem = $hash->{lc $_};
  56         107  
182              
183 56 100 66     188 ($elem or ($elem = $oid->{$_} and $elem->{type} eq $type))
184             ? $elem
185             : undef;
186             } @_;
187              
188 55 100       134 wantarray ? @elem : $elem[0];
189             }
190              
191 44     44 1 533 sub attribute { _get(@_, 'at') }
192 4     4 1 9 sub objectclass { _get(@_, 'oc') }
193 0     0 1 0 sub syntax { _get(@_, 'syn') }
194 4     4 1 9 sub matchingrule { _get(@_, 'mr') }
195 3     3 1 453 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   4 my $schema = shift;
279 2         3 my $entry = shift;
280              
281 2 50       6 return undef unless defined($entry);
282              
283 2         6 keys %type2attr; # reset iterator
284 2         13 while (my($type, $attr) = each %type2attr) {
285 18         59 my $vals = $entry->get_value($attr, asref => 1);
286              
287 18         25 my %names;
288 18         32 $schema->{$type} = \%names; # Save reference to hash of names => element
289              
290 18 100       45 next unless $vals; # Just leave empty ref if nothing
291              
292 10         19 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       1286 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         1861 my %schema_entry = ( type => $type, aliases => [] );
305              
306 828         937 my @tokens;
307 828         1442 pos($val) = 0;
308              
309 828         22226 push @tokens, $+
310             while $val =~ /\G\s*(?:
311             ([()])
312             |
313             ([^"'\s()]+)
314             |
315             "([^"]*)"
316             |
317             '((?:[^']+|'[^\s)])*)'
318             )\s*/xcg;
319 828 50 33     2489 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       1364 shift @tokens if $tokens[0] eq '(';
324 828 50       1246 pop @tokens if $tokens[-1] eq ')';
325              
326             # The first token is the OID
327 828         1371 my $oid = $schema_entry{oid} = shift @tokens;
328              
329 828 50       1355 my $flags = ($type eq 'xat') ? \%xat_flags : \%flags;
330 828         1209 while (@tokens) {
331 2998         3839 my $tag = lc shift @tokens;
332              
333 2998 100       4585 if (exists $flags->{$tag}) {
    50          
334 350         664 $schema_entry{$tag} = 1;
335             }
336             elsif (@tokens) {
337 2648 100       4430 if (($schema_entry{$tag} = shift @tokens) eq '(') {
338 192         214 my @arr;
339 192         229 $schema_entry{$tag} = \@arr;
340 192         192 while (1) {
341 4790         4885 my $tmp = shift @tokens;
342 4790 100       6136 last if $tmp eq ')';
343 4598 100       6452 push @arr, $tmp unless $tmp eq '$';
344              
345             # Drop of end of list ?
346 4598 50       6124 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     5675 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     2159 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       1217 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       1108 if (ref $schema_entry{name}) {
375 44         47 my $aliases;
376 44         39 $schema_entry{name} = shift @{$aliases = $schema_entry{name}};
  44         71  
377 44 50       80 $schema_entry{aliases} = $aliases if @$aliases;
378             }
379              
380             #
381             # Store the elements by OID
382             #
383 828 50       1914 $schema->{oid}->{$oid} = \%schema_entry unless $type eq 'xat';
384              
385             #
386             # We also index elements by name within each type
387             #
388 828         936 foreach my $name ( @{$schema_entry{aliases}}, $schema_entry{name} ) {
  828         1324  
389 874         1127 my $lc_name = lc $name;
390 874         2402 $names{lc $name} = \%schema_entry;
391             }
392             }
393             }
394              
395             # place extendedAttributeInfo into attribute types
396 2 50       9 if (my $xat = $schema->{xat}) {
397 2         8 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         6 $schema->{entry} = $entry;
406 2         25 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 5 $_[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 58 my $self = shift;
445 40         50 my $attr = shift;
446 40         45 my $matchtype = shift;
447              
448 40         64 my $attrtype = $self->attribute( $attr );
449 40 100       83 if (exists $attrtype->{$matchtype}) {
    100          
450 30         78 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         20 $attrtype->{sup}[0],
455             $matchtype);
456             }
457 2         5 return undef;
458             }
459              
460             1;