File Coverage

blib/lib/Connector.pm
Criterion Covered Total %
statement 64 115 55.6
branch 16 40 40.0
condition 3 11 27.2
subroutine 15 26 57.6
pod 10 10 100.0
total 108 202 53.4


line stmt bran cond sub pod time code
1             # Connector
2             #
3             # A generic abstraction for accessing information.
4             #
5             # Written by Scott Hardin, Martin Bartosch and Oliver Welter for the OpenXPKI project 2012
6             #
7              
8             # This is the earliest version we've tested on and we need at least 5.10
9             # because of the '//' operator in one of the sub-modules.
10             use 5.010001;
11 24     24   170198  
  24         87  
12             our $VERSION = '1.45';
13              
14             use strict;
15 24     24   142 use warnings;
  24         38  
  24         461  
16 24     24   169 use English;
  24         39  
  24         525  
17 24     24   123 use Data::Dumper;
  24         45  
  24         179  
18 24     24   8203  
  24         11934  
  24         1143  
19             use Log::Log4perl;
20 24     24   3003  
  24         151144  
  24         214  
21             use Moose;
22 24     24   1725 use Connector::Types;
  24         406996  
  24         150  
23 24     24   141875  
  24         70  
  24         34623  
24             has LOCATION => (
25             is => 'ro',
26             isa => 'Connector::Types::Location',
27             required => 1,
28             );
29              
30             # In order to clear the prefix, call the accessor with undef as argument
31             has PREFIX => (
32             is => 'rw',
33             isa => 'Connector::Types::Key|ArrayRef|Undef',
34             # build and store an array of the prefix in _prefix_path
35             trigger => sub {
36             my ($self, $prefix, $old_prefix) = @_;
37             if (defined $prefix) {
38             my @path = $self->_build_path($prefix);
39             $self->__prefix_path(\@path);
40             } else {
41             $self->__prefix_path([]);
42             }
43             }
44             );
45              
46             has DELIMITER => (
47             is => 'rw',
48             isa => 'Connector::Types::Char',
49             default => '.',
50             );
51              
52             has RECURSEPATH => (
53             is => 'rw',
54             isa => 'Bool',
55             default => '0',
56             );
57              
58             # internal representation of the instance configuration
59             # NB: this should be a private variable and not accessible from outside
60             # an instance.
61             # TODO: figure out how to protect it.
62             has _config => (
63             is => 'rw',
64             lazy => 1,
65             init_arg => undef, # not settable via constructor
66             builder => '_build_config',
67             );
68              
69             has log => (
70             is => 'rw',
71             lazy => 1,
72             init_arg => undef, # not settable via constructor
73             builder => '_build_logger',
74             );
75              
76              
77             # this instance variable is set in the trigger function of PREFIX.
78             # it contains an array representation of PREFIX (assumed to be delimited
79             # by the DELIMITER character)
80             has _prefix_path => (
81             is => 'rw',
82             isa => 'ArrayRef',
83             init_arg => undef,
84             default => sub { [] },
85             writer => '__prefix_path',
86             lazy => 1
87             );
88              
89             # weather to die on undef or just fail silently
90             # implemented in _node_not_exists
91             has 'die_on_undef' => (
92             is => 'rw',
93             isa => 'Bool',
94             default => 0,
95             );
96              
97              
98             # This is the foo that allows us to just milk the connector config from
99             # the settings fetched from another connector.
100              
101             around BUILDARGS => sub {
102             my $orig = shift;
103             my $class = shift;
104              
105             my $args = $_[0];
106              
107             if ( ref($args) eq 'HASH'
108             && defined($args->{CONNECTOR})
109             && defined($args->{TARGET}) ) {
110              
111             my $conn = $args->{CONNECTOR};
112             delete $args->{CONNECTOR};
113              
114             my @targ = $conn->_build_path( $args->{TARGET} );
115             delete $args->{TARGET};
116              
117             my $meta = $class->meta;
118              
119             my $log = $conn->log(); # Logs to the parent that is initialising us
120             $log->trace( 'Wrapping connector - config at ' . join ".", @targ ) ;
121              
122             for my $attr ( $meta->get_all_attributes ) {
123             my $attrname = $attr->name();
124             next if $attrname =~ m/^_/; # skip apparently internal params
125             # allow caller to override params in CONNECTOR
126             if ( not exists($args->{$attrname}) ) {
127             my $meta = $conn->get_meta( [ @targ , $attrname ] );
128             $log->trace( ' Check for ' . $attrname . ' - meta is ' . Dumper $meta );
129             next unless($meta && $meta->{TYPE});
130             if ($meta->{TYPE} eq 'scalar') {
131             $args->{$attrname} = $conn->get( [ @targ , $attrname ] );
132             } elsif ($meta->{TYPE} eq 'list') {
133             my @tmp = $conn->get_list( [ @targ , $attrname ] );
134             $args->{$attrname} = \@tmp;
135             } elsif ($meta->{TYPE} eq 'hash') {
136             $args->{$attrname} = $conn->get_hash( [ @targ , $attrname ] );
137             } else {
138             $log->warn( ' Unexpected type '.$meta->{TYPE}.' for attribute ' . $attrname );
139             }
140             }
141             }
142              
143             $log->trace( 'Wrapping connector - arglist ' .Dumper \@_ );
144             }
145             return $class->$orig(@_);
146             };
147              
148              
149             # subclasses must implement this to initialize _config
150              
151 0     0   0  
152             return Log::Log4perl->get_logger("connector");
153              
154             };
155 51     51   391  
156              
157             # helper function: build a path from the given input. does not take PREFIX
158             # into account
159              
160             my $self = shift;
161             my @arg = @_;
162              
163             my @path;
164 1110     1110   1898  
165 1110         2166  
166             # Catch old call format
167 1110         1412 if (scalar @arg > 1) {
168             die "Sorry, we changed the API (pass scalar or array ref but not array)";
169             }
170              
171 1110 50       2409 my $location = shift @arg;
172 0         0  
173             if (not $location) {
174             @path = ();
175 1110         1691 } elsif (ref $location eq '') {
176             # String path - split at delimiter
177 1110 100       13168 my $delimiter = $self->DELIMITER();
    100          
    50          
    100          
178 20         42 @path = split(/[$delimiter]/, $location);
179             } elsif (ref $location ne "ARRAY") {
180             # Nothing else than arrays allowed beyond this point
181 579         11915 die "Invalid data type passed in argument to _build_path";
182 579         2955 } elsif ($self->RECURSEPATH()) {
183             foreach my $item (@{$location}) {
184             push @path, $self->_build_path( $item );
185 0         0 }
186             } else {
187 4         5 # Atomic path, the array is the result
  4         13  
188 8         32 @path = @{$location};
189             }
190              
191             $self->log()->trace( 'path created ' . Dumper \@path );
192 507         775  
  507         1225  
193             if (wantarray) {
194             return @path;
195 1110         20688 } elsif ($self->RECURSEPATH()) {
196             return join $self->DELIMITER(), @path;
197 1110 100       64909 } else {
    50          
198 1109         4563 die "Sorry, we changed the API, request a list and join yourself or set RECURSEPATH in constructor";
199             }
200 1         34  
201             }
202 0         0  
203             # same as _build_config, but prepends PREFIX
204             my $self = shift;
205             my $location = shift;
206              
207             if (not $location) {
208             return @{$self->_prefix_path()};
209 939     939   1373 } else {
210 939         1291 return (@{$self->_prefix_path()}, ($self->_build_path( $location )));
211             }
212 939 100       2675  
213 21         34 }
  21         568  
214              
215 918         1139 # return the prefix as string (using DELIMITER)
  918         19276  
216             my $self = shift;
217             return join($self->DELIMITER(), @{$self->_prefix_path()});
218             }
219              
220             # This is a helper to handle non exisiting nodes
221             # By default we just return undef but you can configure the connector
222 0     0   0 # to die with an error
223 0         0 my $self = shift;
  0         0  
224             my $path = shift || '';
225             $path = join ("|", @{$path}) if (ref $path eq "ARRAY");
226              
227             $self->log()->debug('Node does not exist at ' . $path );
228              
229             if ($self->die_on_undef()) {
230 116     116   280 confess("Node does not exist at " . $path );
231 116   100     452 }
232 116 100       420  
  8         20  
233             return;
234 116         2435 }
235              
236 116 50       3448 my $self = shift;
237 0         0 my $message = shift;
238             my $log_message = shift || $message;
239              
240 116         662 $self->log()->error($log_message);
241             die $message;
242              
243             }
244 1     1   2  
245 1         3  
246 1   33     17 # Subclasses can implement these to save resources
247              
248 1         23 my $self = shift;
249 1         620 my @node = $self->get_list( shift );
250              
251             if (!@node) {
252             return 0;
253             }
254             return scalar @node;
255             }
256              
257 0     0 1    
258 0           my $self = shift;
259             my $node = $self->get_hash( shift );
260 0 0          
261 0           if (!defined $node) {
262             return @{[]};
263 0           }
264              
265             if (ref $node ne "HASH") {
266             die "requested path looks not like a hash";
267             }
268 0     0 1    
269 0           return keys (%{$node});
270             }
271 0 0          
272 0           # Generic, should be implemented in child classes to save resources
  0            
273              
274             my $self = shift;
275 0 0         my @args = @_;
276 0           my @path = $self->_build_path_with_prefix( $args[0] );
277             my $meta;
278             my $result;
279 0            
  0            
280             eval {
281              
282             $meta = $self->get_meta( @args );
283              
284             if (!$meta || !$meta->{TYPE}) {
285 0     0 1   $result = undef;
286 0           } elsif ($meta->{TYPE} eq 'scalar') {
287 0           $result = defined $self->get( @args );
288 0           } elsif ($meta->{TYPE} eq 'list') {
289             my @tmp = $self->get_list( @args );
290             $result = (@tmp && scalar @tmp > 0);
291 0           } elsif ($meta->{TYPE} eq 'hash') {
292             $result = defined $self->get_hash( @args );
293 0           } elsif ($meta->{TYPE} eq 'connector') {
294             $result = 1;
295 0 0 0       } elsif ($meta->{TYPE} eq 'reference') {
    0          
    0          
    0          
    0          
    0          
296 0           $result = 1;
297             } else {
298 0           $self->log()->warn( ' Unexpected type '.$meta->{TYPE}.' for exist on path ' . join ".", @path );
299             }
300 0           };
301 0   0        
302             $self->log()->debug("Got eval error ($EVAL_ERROR) for exist on path " . join ".", @path ) if ($EVAL_ERROR);
303 0            
304             return $result;
305 0           }
306              
307 0           # subclasses must implement get and/or set in order to do something useful
308              
309 0            
310             no Moose;
311             __PACKAGE__->meta->make_immutable;
312              
313 0 0         1;
314              
315 0           =head1 NAME
316              
317             Connector - a generic connection to a hierarchical-structured data set
318              
319 0     0 1   =head1 DESCRIPTION
  0            
320 0     0 1    
  0            
321 0     0 1   The Connector is generic connection to a data set, typically configuration
  0            
322 0     0 1   data in a hierarchical structure. Each connector object accepts the get(KEY)
  0            
323 0     0 1   method, which, when given a key, returns the associated value from the
  0            
324 0     0 1   connector's data source.
  0            
325       7 1    
326             Typically, a connector acts as a proxy to a simple data source like
327             YAML, Config::Std, Config::Versioned, or to a more complex data source
328 24     24   241 like an LDAP server or Proc::SafeExec. The standard calling convention
  24         43  
  24         143  
329             via get(KEY) makes the connectors interchangeable.
330              
331             In addition, a set of meta-connectors may be used to combine multiple
332             connectors into more complex chains. The Connector::Multi, for example,
333             allows for redirection to delegate connectors via symbolic links. If
334             you have a list of connectors and want to use them in a load-balancing,
335             round-robin fashion or have the list iterated until a value is found,
336             use Connector::List and choose the algorithm to perform.
337              
338             =head1 SYNOPSIS
339              
340             use Connector::MODULENAME;
341              
342             my $conn = Connector::MODULENAME->new( {
343             LOCATION => $path_to_config_for_module,
344             });
345              
346             my $val = $conn->get('full.name.of.key');
347              
348             =head2 Connector Class
349              
350             This is the base class for all Connector implementations. It provides
351             common helper methods and performs common sanity checking.
352              
353             Usually this class should not be instantiated directly.
354              
355             =head1 CONFIGURATION
356              
357             =head2 die_on_undef
358              
359             Set to true if you want the connector to die when a query reaches a non-exisiting
360             node. This will affect calls to get/get_list/get_hash and will not affect
361             values that are explicitly set to undef (if supported by the connector!).
362              
363             =head1 Accessor Methods
364              
365             Each accessor method is valid only on special types of nodes. If you call them
366             on a wrong type of node, the connector may retunr unexpected result or simply die.
367              
368             =head2 exists
369              
370             =head2 get
371              
372             Basic method to obtain a scalar value at the leaf of the config tree.
373              
374             my $value = $connector->get('smartcard.owners.tokenid.bob');
375              
376             Each implementation must also accept an arrayref as path. The path is
377             contructed from the elements. The default behaviour allows strings using
378             the delimiter character inside an array element. If you want each array
379             element to be parsed, you need to pass "RECURSEPATH => 1" to the constructor.
380              
381             my $value = $connector->get( [ 'smartcard','owners','tokenid','bob.builder' ] );
382              
383             Some implementations accept control parameters, which can be passed by
384             I<params>, which is a hash ref of key => value pairs.
385              
386             my $value = $connector->get( 'smartcard.owners.tokenid.bob' , { version => 1 } );
387              
388             =head2 get_list
389              
390             This method is only valid if it is called on a "n-1" depth node representing
391             an ordered list of items (array). The return value is an array with all
392             values present below the node.
393              
394             my @items = $connector->get_list( 'smartcard.owners.tokenid' );
395              
396              
397             =head2 get_size
398              
399             This method is only valid if it is called on a "n-1" depth node representing
400             an ordered list of items (array). The return value is the number of elements
401             in this array (including undef elements if they are explicitly given).
402              
403             my $count = $connector->get_size( 'smartcard.owners.tokens.bob' );
404              
405             If the node does not exist, 0 is returned.
406              
407             =head2 get_hash
408              
409             This method is only valid if it is called on a "n-1" depth node representing
410             a key => value list (hash). The return value is a hash ref.
411              
412             my %data = %{$connector->get_hash( 'smartcard.owners.tokens.bob' )};
413              
414              
415             =head2 get_keys
416              
417             This method is only valid if it is called on a "n-1" depth node representing
418             a key => value list (hash). The return value is an array holding the
419             values of all keys (including undef elements if they are explicitly given).
420              
421             my @keys = $connector->get_keys( 'smartcard.owners.tokens.bob' );
422              
423             If the node does not exist, an empty list is returned.
424              
425             =head2 get_reference
426              
427             Rarely used, returns the value of a reference node. Currently used by
428             Connector::Multi in combination with Connector::Proxy::Config::Versioned
429             to create internal links and cascaded connectors. See Connector::Multi
430             for details.
431              
432             =head2 set
433              
434             The set method is a "all in one" implementation, that is used for either type
435             of value. If the value is not a scalar, it must be passed by reference.
436              
437             $connector->set('smartcard.owners.tokenid.bob', $value, $params);
438              
439             The I<value> parameter holds a scalar or ref to an array/hash with the data to
440             be written. I<params> is a hash ref which holds additional parameters for the
441             operation and can be undef if not needed.
442              
443             =head1 STRUCTURAL METHODS
444              
445             =head2 get_meta
446              
447             This method returns some structural information about the current node as
448             hash ref. At minimum it must return the type of node at the current path.
449              
450             Valid values are I<scalar, list, hash, reference>. The types match the
451             accessor methods given above (use C<get> for I<scalar>).
452              
453             my $meta = $connector->get_meta( 'smartcard.owners' );
454             my $type = $meta->{TYPE};
455              
456             When you call a proxy connector without sufficient arguments to perform the
457             query, you will receive a value of I<connector> for type. Running a get_*
458             method against such a node will cause the connector to die!
459              
460             =head2 cleanup
461              
462             Advise connectors to close, release or flush any open handle or sessions.
463             Should be called directly before the program terminates. Connectors might
464             be stale and not respond any longer after this was called.
465              
466             =head1 IMPLEMENTATION GUIDELINES
467              
468             You SHOULD use the _node_not_exists method if the requested path does not exist
469             or has an undefined value. This will internally take care of the I<die_on_undef>
470             setting and throw an exception or return undef. So you can just write:
471              
472             if (path not exists || not defined val) {
473             return $self->_node_not_exists( pathspec );
474             }
475              
476             As connectors are often used in eval constructs where the error messages
477             are swallowed you SHOULD log a verbose error before aborting with
478             die/confess. You can use the _log_and_die method for this purpose. It will
479             send a message to the logger on error level before calling "die $message".
480              
481             =head2 path building
482              
483             You should always pass the first parameter to the private C<_build_path>
484             method. This method converts any valid path spec representation to a valid
485             path. It takes care of the RECURSEPATH setting and returns the path
486             elements as list.
487              
488             =head2 Supported methods
489              
490             The methods get, get_list, get_size, get_hash, get_keys, set, get_meta are
491             routed to the appropriate connector.
492              
493             You MUST implement at minimum one of the three data getters, if get_list/get_keys
494             is omited, the base class will do a get_list/get_keys call and return the info
495             which will be a correct result but might be expensive, so you can provide your
496             own implementiation if required.
497              
498             You MUST also implement the get_meta method. If you have a connector with a
499             fixed type, you MAY check if the particular path exists and return
500             the result of I<_node_not_exists>.
501              
502             =head2 cleanup
503              
504             Connectors that keep locks or use long-lived sessions that are not
505             bound to the lifetime of the perl process should implement this method
506             and cleanup their mess. While it would be nice, that connectors can be
507             revived after cleanup was called, this is not a strict requirement.
508              
509             =head1 AUTHORS
510              
511             Scott Hardin <mrscotty@cpan.org>
512              
513             Martin Bartosch
514              
515             Oliver Welter
516              
517             =head1 COPYRIGHT
518              
519             Copyright 2013/2021 White Rabbit Security Gmbh
520              
521             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.