96a5977efd9f31190c067fa653125e469a767db0
[licom.git] / lib / Person.pm
1 package Person;
2
3 use strict;
4 use warnings;
5
6 use Net::LDAP;
7 use Net::LDAP::Filter;
8
9 =head1 NAME
10
11 Person - High level interface for address books using an LDAP-backend.
12
13 =cut
14
15 our %Config =
16 (
17         base_dn         => undef
18 );
19
20 our %ValidFields =
21 (
22         telephoneNumber                 => 1,
23         facsimileTelephoneNumber        => 1,
24         sn                              => 0,
25         cn                              => 0,
26         givenName                       => 0,
27         homePhone                       => 1,
28         homePostalAddress               => 1,
29         labeledURI                      => 1,
30         mail                            => 1,
31         mobile                          => 1,
32         o                               => 1
33 );
34
35 our %ExternalNames =
36 (
37         officephone     => 'telephoneNumber',
38         fax             => 'facsimileTelephoneNumber',
39         lastname        => 'sn',
40         name            => 'cn',
41         firstname       => 'givenName',
42         homephone       => 'homePhone',
43         address         => 'homePostalAddress',
44         uri             => 'labeledURI',
45         mail            => 'mail',
46         cellphone       => 'mobile',
47         group           => 'o'
48 );
49
50 our $Ldap;
51
52 return (1);
53
54 =head1 METHODS
55
56 =over 4
57
58 =item Person-E<gt>B<connect> (I<$server>, I<$bind_dn>, I<$password>, I<$base_dn>, [I<$port>])
59
60 Connects to the LDAP-Server given.
61
62 =cut
63
64 sub connect
65 {
66         my $pkg = shift;
67         my %opts = @_;
68
69         my $bind_dn = $opts{'bind_dn'};
70         my $base_dn = $opts{'base_dn'};
71         my $uri     = $opts{'uri'};
72         my $passwd  = $opts{'password'};
73
74         my $msg;
75
76         $Ldap = Net::LDAP->new ($uri);
77
78         $msg = $Ldap->bind ($bind_dn, password => $passwd);
79         if ($msg->is_error ())
80         {
81                 warn ('LDAP bind failed: ' . $msg->error_text ());
82                 return (0);
83         }
84
85         $Config{'base_dn'} = $base_dn;
86
87         return (1);
88 }
89
90 =item Person-E<gt>B<disconnect> ()
91
92 Disconnect from the LDAP-Server.
93
94 =cut
95
96 sub disconnect
97 {
98         $Ldap->unbind ();
99         $Ldap = undef;
100 }
101
102 =item Person-E<gt>B<new> (I<$ldap_entry>)
103
104 Created a new I<Person>-object from the passed I<Net::LDAP::Entry>-object.
105
106 =cut
107
108 sub new
109 {
110         my $pkg = shift;
111         my $entry = shift;
112         my $obj = {};
113
114         $obj->{'dn'} = $entry->dn ();
115         $obj->{'ldap'} = $entry;
116
117         for (keys %ValidFields)
118         {
119                 my $key = $_;
120                 $obj->{$key} = $entry->get_value ($key, asref => $ValidFields{$key});
121         }
122
123         return (bless ($obj, $pkg));
124 }
125
126 =item Person-E<gt>B<load> (I<$cn>)
127
128 Loads the given CN and returns the B<Person>-object.
129
130 =cut
131
132 sub load
133 {
134         my $pkg = shift;
135         my $cn = shift;
136
137         my ($retval) = search ($pkg, [[cn => $cn]]);
138
139         if (!$retval)
140         {
141                 warn ("CN '$cn' could not be found");
142                 return (undef);
143         }
144         
145         return ($retval);
146 }
147
148 =item Person-E<gt>B<create> (B<lastname> =E<gt> I<$lastname>, B<firstname> =E<gt> I<$firstname>, ...)
149
150 Create a new I<Net::LDAP::Entry>-object and return it's corresponding
151 I<Person>-object.
152
153 =cut
154
155 sub create
156 {
157         my $pkg = shift;
158
159         my %hash = @_;
160         my $entry = Net::LDAP::Entry->new ();
161         my $dn;
162         my $ou;
163
164         $entry->add (objectClass => [qw(top organizationalUnit person organizationalPerson inetOrgPerson)]);
165
166         for (keys %hash)
167         {
168                 my $key = $_;
169                 my $val = $hash{$key};
170                 my $field = defined ($ExternalNames{$key}) ? $ExternalNames{$key} : $key;
171                 
172                 if (!defined ($ValidFields{$field}))
173                 {
174                         warn ("Invalid field $field");
175                         next;
176                 }
177
178                 if ($ValidFields{$field})
179                 {
180                         if (ref ($val) eq 'ARRAY')
181                         {
182                                 $entry->add ($field => [@$val]);
183                         }
184                         elsif (!ref ($val))
185                         {
186                                 $entry->add ($field => [$val]);
187                         }
188                         else
189                         {
190                                 warn ("You cannot pass ref-type " . ref ($val));
191                         }
192                 }
193                 else
194                 {
195                         my $temp;
196                         if (ref ($val) eq 'ARRAY')
197                         {
198                                 $temp = $val->[0];
199                         }
200                         elsif (!ref ($val))
201                         {
202                                 $temp = $val;
203                         }
204                         else
205                         {
206                                 warn ("You cannot pass ref-type " . ref ($val));
207                         }
208
209                         $entry->add ($field => $val) if (defined ($val) and $val);
210                 }
211         }
212
213         my $sn = $entry->get_value ('sn');
214         my $gn = $entry->get_value ('givenName');
215
216         if (!defined ($sn) or !defined ($gn))
217         {
218                 warn ("sn or givenName not given");
219                 return (undef);
220         }
221
222         $dn = "cn=$sn $gn," . $Config{'base_dn'};
223         ($ou) = $Config{'base_dn'} =~ m/\bou\s*=\s*([^,]+)/i;
224         
225         $entry->add (cn => "$sn $gn", ou => $ou);
226         $entry->dn ($dn);
227
228         print "<!--\n";
229         $entry->dump (*STDOUT);
230         print "-->\n";
231
232         $entry->changetype ('add');
233         my $mesg = $entry->update ($Ldap);
234
235         if ($mesg->is_error ())
236         {
237                 warn ("Error while creating entry '$dn' on LDAP server: " . $mesg->error_text ());
238                 return (undef);
239         }
240
241         return (new ($pkg, $entry));
242 }
243
244 =item Person-E<gt>B<search> (B<firstname> =E<gt> I<"Flor*">)
245
246 Search for the given patterns. Returns a list of I<Person>-objects.
247
248   @filter =
249   (
250     [
251       [field => value], # OR
252       [field => value]
253     ], # AND
254     ...
255   );
256
257 =cut
258
259 sub search
260 {
261         my $pkg = shift;
262
263         my @patterns = @_;
264         my @konjunct = ();
265         my $filter;
266
267         my $mesg;
268         my @retval = ();
269
270         for (@patterns)
271         {
272                 my $dj = $_;
273                 my @disjunc = ();
274
275                 for (@$dj)
276                 {
277                         my $field = $_->[0];
278                         my $value = $_->[1];
279
280                         $field = $ExternalNames{$field} if (defined ($ExternalNames{$field}));
281                         if (!defined ($ValidFields{$field}))
282                         {
283                                 warn ("Not a valid field: $field");
284                                 next;
285                         }
286
287                         $value =~ s/([\(\)\\])/\\$1/g;
288
289                         push (@disjunc, "($field=$value)");
290                 }
291                         
292                 if (@disjunc)
293                 {
294                         push (@konjunct, join ('', '(|', @disjunc, ')'));
295                 }
296         }
297
298         if (@konjunct)
299         {
300                 $filter = join ('', '(&(objectclass=inetOrgPerson)', @konjunct, ')');
301         }
302         else
303         {
304                 $filter = '(objectclass=inetOrgPerson)';
305         }
306
307         print STDERR "Debug: using filter: $filter";
308         
309         $mesg = $Ldap->search
310         (
311                 base   => $Config{'base_dn'},
312                 filter => $filter
313         );
314
315         if ($mesg->is_error ())
316         {
317                 warn ("Error while querying LDAP server: " . $mesg->error_text ());
318                 return (qw());
319         }
320
321         for ($mesg->entries ())
322         {
323                 my $entry = $_;
324                 my $obj = new ($pkg, $entry);
325
326                 push (@retval, $obj);
327         }
328
329         return (@retval);
330 }
331
332 =item I<$obj>-E<gt>B<delete> ()
333
334 Deletes the record.
335
336 =cut
337
338 sub delete
339 {
340         my $obj = shift;
341         my $entry = $obj->{'ldap'};
342
343         $entry->changetype ('delete');
344         $entry->delete ();
345         $entry->update ($Ldap);
346
347         %$obj = ();
348 }
349
350 =item I<$obj>-E<gt>B<lastname> ([I<$lastname>])
351
352 Get or set the lastname.
353
354 =cut
355
356 sub _update_dn
357 {
358         my $obj = shift;
359         my $entry = $obj->{'ldap'};
360         my $sn = $obj->{'sn'};
361         my $gn = $obj->{'givenName'};
362         my $cn = "$sn $gn";
363         my $dn = "cn=$cn," . $Config{'base_dn'};
364
365         $obj->{'cn'} = $cn;
366
367         $entry->changetype ('modify');
368         $entry->replace (sn => $sn, givenName => $gn, cn => $cn);
369         $entry->dn ($dn);
370         $entry->update ($Ldap);
371 }
372
373 sub lastname
374 {
375         my $obj = shift;
376
377         if (@_)
378         {
379                 $obj->{'sn'} = shift;
380                 _update_dn ($obj);
381         }
382
383         return ($obj->{'sn'});
384 }
385
386 =item I<$obj>-E<gt>B<firstname> ([I<$firstname>])
387
388 Get or set the firstname.
389
390 =cut
391
392 sub firstname
393 {
394         my $obj = shift;
395
396         if (@_)
397         {
398                 $obj->{'givenName'} = shift;
399                 _update_dn ($obj);
400         }
401
402         return ($obj->{'givenName'});
403 }
404
405 =item I<$obj>-E<gt>B<name> ()
406
407 Returns the CN.
408
409 =cut
410
411 sub name
412 {
413         my $obj = shift;
414         return ($obj->{'cn'});
415 }
416
417 =item I<$obj>-E<gt>B<address> ([I<@address>])
418
419 =item I<$obj>-E<gt>B<homephone> ([I<@homephone>])
420
421 =item I<$obj>-E<gt>B<cellphone> ([I<@cellphone>])
422
423 =item I<$obj>-E<gt>B<officephone> ([I<@officephone>])
424
425 =item I<$obj>-E<gt>B<fax> ([I<@fax>])
426
427 =item I<$obj>-E<gt>B<mail> ([I<@mail>])
428
429 =item I<$obj>-E<gt>B<uri> ([I<@uri>])
430
431 =item I<$obj>-E<gt>B<group> ([I<@groups>])
432
433 Get or set the attribute.
434
435 =cut
436
437 sub AUTOLOAD
438 {
439         my $obj = shift;
440         my @values = @_;
441         my $field = $Person::AUTOLOAD;
442         $field =~ s/.*:://;
443
444         return (set ($obj, $field, @values ? [@values] : undef))
445 }
446
447 sub get
448 {
449         my $obj = shift;
450         my $field = shift;
451
452         return (set ($obj, $field, undef));
453 }
454
455 sub set
456 {
457         my $obj = shift;
458         my $field = shift;
459         my $value = @_ ? shift : undef;
460         my $entry = $obj->{'ldap'};
461         
462         if (defined ($ExternalNames{$field}))
463         {
464                 $field = $ExternalNames{$field};
465         }
466         if (!defined ($ValidFields{$field}))
467         {
468                 return (undef);
469         }
470
471         if (defined ($value))
472         {
473                 $entry->changetype ('modify');
474
475                 if ($ValidFields{$field})
476                 {
477                         $entry->replace ($field, [@$value]);
478                         $obj->{$field} = $value;
479                 }
480                 else
481                 {
482                         splice (@$value, 1) if (scalar (@$value) > 1);
483                         $entry->replace ($field, $value);
484                         $obj->{$field} = $value->[0];
485                 }
486
487                 $entry->update ($Ldap);
488         }
489
490         $obj->{$field} = [] unless (defined ($obj->{$field}));
491         
492         if (wantarray () and $ValidFields{$field})
493         {
494                 return (@{$obj->{$field}});
495         }
496         else
497         {
498                 return ($obj->{$field});
499         }
500 }
501
502 sub get_user
503 {
504         my $pkg = shift;
505         my $dn = shift;
506         my ($search) = $dn =~ m/cn\s*=\s*([^,]+)/i;
507
508         die unless ($search);
509         
510         my $cn = '';
511         my $id = '';
512
513         my $mesg = $Ldap->search
514         (
515                 base   => $Config{'base_dn'},
516                 filter => "(cn=$search)"
517         );
518
519         if ($mesg->is_error ())
520         {
521                 warn ("Error while querying LDAP server: " . $mesg->error_text ());
522                 return ('');
523         }
524
525         for ($mesg->entries ())
526         {
527                 my $e = $_;
528                 my ($t_cn) = $e->get_value ('cn', asref => 0);
529                 my ($t_id) = $e->get_value ('uid', asref => 0);
530
531                 print STDERR "LDAP result: $t_cn, $t_id";
532
533                 if (!$id or $t_id)
534                 {
535                         $cn = $t_cn;
536                         $id = $t_id;
537                 }
538         }
539
540         return ($cn, $id);
541 }
542
543 =back
544
545 =head1 AUTHOR
546
547 Florian octo Forster E<lt>octo at verplant.orgE<gt>
548
549 =cut