File Coverage

blib/lib/Apache/Session/Browseable/Store/LDAP.pm
Criterion Covered Total %
statement 6 105 5.7
branch 0 56 0.0
condition 0 56 0.0
subroutine 2 9 22.2
pod 0 7 0.0
total 8 233 3.4


line stmt bran cond sub pod time code
1             package Apache::Session::Browseable::Store::LDAP;
2              
3 1     1   200662 use strict;
  1         3  
  1         30  
4 1     1   6 use Net::LDAP;
  1         4  
  1         7  
5              
6             our $VERSION = '1.3.8';
7              
8             sub new {
9 0     0 0   my $class = shift;
10 0           return bless {}, $class;
11             }
12              
13             sub insert {
14 0     0 0   my $self = shift;
15 0           my $session = shift;
16 0           $self->{args} = $session->{args};
17 0   0       $self->{args}->{ldapObjectClass} ||= 'applicationProcess';
18 0   0       $self->{args}->{ldapAttributeId} ||= 'cn';
19 0   0       $self->{args}->{ldapAttributeContent} ||= 'description';
20 0   0       $self->{args}->{ldapAttributeIndex} ||= 'ou';
21              
22             my $index =
23             ref( $session->{args}->{Index} )
24             ? $session->{args}->{Index}
25 0 0         : [ split /\s+/, $session->{args}->{Index} ];
26 0           my $id = $session->{data}->{_session_id};
27              
28 0           my $attrIndex;
29 0           foreach my $i (@$index) {
30 0           my $t;
31 0 0         next unless ( $t = $session->{data}->{$i} );
32 0           push @$attrIndex, "${i}_$t";
33             }
34             my $attrs = [
35             objectClass => $self->{args}->{ldapObjectClass},
36             $self->{args}->{ldapAttributeId} => $session->{data}->{_session_id},
37             $self->{args}->{ldapAttributeContent} => $session->{serialized},
38 0           ];
39 0 0         push @$attrs, ( $self->{args}->{ldapAttributeIndex} => $attrIndex )
40             if ($attrIndex);
41              
42             my $msg = $self->ldap->add(
43             $self->{args}->{ldapAttributeId} . "=$id,"
44             . $self->{args}->{ldapConfBase},
45 0           attrs => $attrs,
46             );
47              
48 0 0         $self->ldap->unbind() && delete $self->{ldap};
49 0 0         $self->logError($msg) if ( $msg->code );
50             }
51              
52             sub update {
53 0     0 0   my $self = shift;
54 0           my $session = shift;
55 0           $self->{args} = $session->{args};
56 0   0       $self->{args}->{ldapObjectClass} ||= 'applicationProcess';
57 0   0       $self->{args}->{ldapAttributeId} ||= 'cn';
58 0   0       $self->{args}->{ldapAttributeContent} ||= 'description';
59 0   0       $self->{args}->{ldapAttributeIndex} ||= 'ou';
60              
61             my $index =
62             ref( $session->{args}->{Index} )
63             ? $session->{args}->{Index}
64 0 0         : [ split /\s+/, $session->{args}->{Index} ];
65 0           my $id = $session->{data}->{_session_id};
66              
67 0           my $attrIndex;
68 0           foreach my $i (@$index) {
69 0           my $t;
70 0 0         next unless ( $t = $session->{data}->{$i} );
71 0           push @$attrIndex, "${i}_$t";
72             }
73              
74             my $attrs =
75 0           { $self->{args}->{ldapAttributeContent} => $session->{serialized} };
76 0 0         $attrs->{ $self->{args}->{ldapAttributeIndex} } = $attrIndex
77             if ($attrIndex);
78              
79             my $msg = $self->ldap->modify(
80             $self->{args}->{ldapAttributeId} . "="
81             . $session->{data}->{_session_id} . ","
82             . $self->{args}->{ldapConfBase},
83 0           replace => $attrs,
84             );
85              
86 0 0         $self->ldap->unbind() && delete $self->{ldap};
87 0 0         $self->logError($msg) if ( $msg->code );
88             }
89              
90             sub materialize {
91 0     0 0   my $self = shift;
92 0           my $session = shift;
93 0           $self->{args} = $session->{args};
94 0   0       $self->{args}->{ldapObjectClass} ||= 'applicationProcess';
95 0   0       $self->{args}->{ldapAttributeId} ||= 'cn';
96 0   0       $self->{args}->{ldapAttributeContent} ||= 'description';
97 0   0       $self->{args}->{ldapAttributeIndex} ||= 'ou';
98              
99             my $msg = $self->ldap->search(
100             base => $self->{args}->{ldapAttributeId} . "="
101             . $session->{data}->{_session_id} . ","
102             . $self->{args}->{ldapConfBase},
103             filter => '(objectClass=' . $self->{args}->{ldapObjectClass} . ')',
104             scope => 'base',
105 0           attrs => [ $self->{args}->{ldapAttributeContent} ],
106             );
107              
108 0 0         $self->ldap->unbind() && delete $self->{ldap};
109 0 0         $self->logError($msg) if ( $msg->code );
110              
111 0           eval {
112             $session->{serialized} = $msg->shift_entry()
113 0           ->get_value( $self->{args}->{ldapAttributeContent} );
114             };
115              
116 0 0         if ( !defined $session->{serialized} ) {
117 0           die "Object does not exist in data store";
118             }
119             }
120              
121             sub remove {
122 0     0 0   my $self = shift;
123 0           my $session = shift;
124 0           $self->{args} = $session->{args};
125 0   0       $self->{args}->{ldapObjectClass} ||= 'applicationProcess';
126 0   0       $self->{args}->{ldapAttributeId} ||= 'cn';
127 0   0       $self->{args}->{ldapAttributeContent} ||= 'description';
128 0   0       $self->{args}->{ldapAttributeIndex} ||= 'ou';
129              
130             $self->ldap->delete( $self->{args}->{ldapAttributeId} . "="
131             . $session->{data}->{_session_id} . ","
132 0           . $self->{args}->{ldapConfBase} );
133              
134 0 0         $self->ldap->unbind() && delete $self->{ldap};
135             }
136              
137             sub ldap {
138 0     0 0   my $self = shift;
139 0 0         return $self->{ldap} if ( $self->{ldap} );
140              
141             # Parse servers configuration
142 0           my $useTls = 0;
143 0           my $tlsParam;
144 0           my @servers = ();
145 0           foreach my $server ( split /[\s,]+/, $self->{args}->{ldapServer} ) {
146 0 0         if ( $server =~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) {
147 0           $useTls = 1;
148 0           $server = $1;
149 0   0       $tlsParam = $2 || "";
150             }
151             else {
152 0           $useTls = 0;
153             }
154 0           push @servers, $server;
155             }
156              
157             # Compatibility
158 0   0       my $caFile = $self->{args}->{ldapCAFile} || $self->{args}->{caFile};
159 0   0       my $caPath = $self->{args}->{ldapCAPath} || $self->{args}->{caPath};
160              
161             # Connect
162             my $ldap = Net::LDAP->new(
163             \@servers,
164             onerror => undef,
165             verify => $self->{args}->{ldapVerify} || "require",
166             ( $caFile ? ( cafile => $caFile ) : () ),
167             ( $caPath ? ( capath => $caPath ) : () ),
168              
169             (
170             $self->{args}->{ldapRaw} ? ( raw => $self->{args}->{ldapRaw} )
171             : ()
172             ),
173             (
174             $self->{args}->{ldapPort} ? ( port => $self->{args}->{ldapPort} )
175 0 0 0       : ()
    0          
    0          
    0          
    0          
176             ),
177             ) or die( 'Unable to connect to ' . join( ' ', @servers ) . ": " . $@ );
178              
179             # Check SSL error for old Net::LDAP versions
180 0 0         if ( $Net::LDAP::VERSION < '0.64' ) {
181              
182             # CentOS7 has a bug in which IO::Socket::SSL will return a broken
183             # socket when certificate validation fails. Net::LDAP does not catch
184             # it, and the process ends up crashing.
185             # As a precaution, make sure the underlying socket is doing fine:
186 0 0 0       if ( $ldap->socket->isa('IO::Socket::SSL')
187             and $ldap->socket->errstr < 0 )
188             {
189 0           die( "SSL connection error: " . $ldap->socket->errstr );
190             }
191             }
192              
193             # Start TLS if needed
194 0 0         if ($useTls) {
195 0           my %h = split( /[&=]/, $tlsParam );
196 0   0       $h{verify} ||= ( $self->{args}->{ldapVerify} || "require" );
      0        
197 0 0 0       $h{cafile} ||= $caFile if ($caFile);
198 0 0 0       $h{capath} ||= $caPath if ($caPath);
199 0           my $start_tls = $ldap->start_tls(%h);
200 0 0         if ( $start_tls->code ) {
201 0           $self->logError($start_tls);
202 0           return;
203             }
204             }
205              
206             # Bind with credentials
207             my $bind = $ldap->bind( $self->{args}->{ldapBindDN},
208 0           password => $self->{args}->{ldapBindPassword} );
209 0 0         if ( $bind->code ) {
210 0           $self->logError($bind);
211 0           return;
212             }
213              
214 0           $self->{ldap} = $ldap;
215 0           return $ldap;
216             }
217              
218             sub logError {
219 0     0 0   my $self = shift;
220 0           my $ldap_operation = shift;
221 0           die "LDAP error " . $ldap_operation->code . ": " . $ldap_operation->error;
222             }
223              
224             1;
225              
226             =pod
227              
228             =head1 NAME
229              
230             Apache::Session::Browseable::Store::LDAP - Use LDAP to store persistent objects
231              
232             =head1 SYNOPSIS
233              
234             use Apache::Session::Browseable::Store::LDAP;
235              
236             my $store = new Apache::Session::Browseable::Store::LDAP;
237              
238             $store->insert($ref);
239             $store->update($ref);
240             $store->materialize($ref);
241             $store->remove($ref);
242              
243             =head1 DESCRIPTION
244              
245             This module fulfills the storage interface of Apache::Session. The serialized
246             objects are stored in an LDAP directory file using the Net::LDAP Perl module.
247              
248             =head1 OPTIONS
249              
250             This module requires one argument in the usual Apache::Session style. The
251             keys ldapServer, ldapBase, ldapBindDN, ldapBindPassword are required. The key
252             ldapPort, ldapObjectClass, ldapAttributeId, ldapAttributeContent,
253             ldapAttributeIndex, and ldapRaw are optional.
254             Example:
255              
256             tie %s, 'Apache::Session::Browseable::LDAP', undef,
257             {
258             ldapServer => 'localhost',
259             ldapBase => 'dc=example,dc=com',
260             ldapBindDN => 'cn=admin,dc=example,dc=com',
261             ldapBindPassword => 'pass',
262             Index => 'uid ipAddr',
263             ldapObjectClass => 'applicationProcess',
264             ldapAttributeId => 'cn',
265             ldapAttributeContent => 'description',
266             ldapAttributeIndex => 'ou',
267             ldapRaw => '(?i:^jpegPhoto|;binary)',
268             };
269              
270             =head1 AUTHOR
271              
272             Xavier Guimard, Eguimard@E
273              
274             =head1 COPYRIGHT AND LICENSE
275              
276             Copyright (C) 2010-2017 by Xavier Guimard
277             Copyright (C) 2015-2017 by Clement Oudot
278              
279             This library is free software; you can redistribute it and/or modify
280             it under the same terms as Perl itself, either Perl version 5.10.0 or,
281             at your option, any later version of Perl 5 you may have available.
282              
283             =head1 SEE ALSO
284              
285             L
286              
287             =cut