source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/User.pm @ 2267

Last change on this file since 2267 was 2267, checked in by nanardon, 5 years ago

Add a way to dump object and all related objects

  • Property svn:keywords set to Id Rev
File size: 80.8 KB
Line 
1package LATMOS::Accounts::Bases::Sql::User;
2
3use 5.010000;
4use strict;
5use warnings;
6use overload '""' => 'stringify';
7
8use LATMOS::Accounts::Utils;
9use LATMOS::Accounts::Log;
10use POSIX qw(strftime);
11use Date::Parse qw(str2time);
12use DateTime;
13use DateTime::TimeZone;
14use base qw(LATMOS::Accounts::Bases::Sql::objects);
15use LATMOS::Accounts::I18N;
16use Date::Calc;
17use LATMOS::Accounts::Mail;
18
19our $VERSION = (q$Rev: 2158 $ =~ /^Rev: (\d+) /)[0];
20
21=head1 NAME
22
23LATMOS::Ad - Perl extension for blah blah blah
24
25=head1 DESCRIPTION
26
27Account base access over standard unix file format.
28
29=head1 FUNCTIONS
30
31=cut
32
33=head2 new(%config)
34
35Create a new LATMOS::Ad object for windows AD $domain.
36
37domain / server: either the Ad domain or directly the server
38
39ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
40
41=cut
42
43sub _object_table { 'user' }
44
45sub _key_field { 'name' }
46
47sub _has_extended_attributes { 1 }
48
49sub stringify {
50    my ($self) = @_;
51
52    return join(' ', grep { $_ }
53        (
54            $self->get_field('givenName'),
55            $self->get_field('sn')
56        )
57    )
58    || $self->get_field('description')
59    || $self->id;
60}
61
62sub GetOtypeDef {
63    my ($class) = @_;
64
65    {
66        address => 'user',
67        employment => 'user',
68        nethost => [ 'owner', 'user' ],
69    },
70}
71
72sub _get_attr_schema {
73    my ($class, $base) = @_;
74
75    my $subsetaddress = sub {
76        my ($self, $data) = @_;
77        my $fmainaddress = $self->object->_get_c_field('mainaddress');
78
79        # set address attribute => create address object on the fly
80        # except if attr is empty !
81        if (!$fmainaddress && $data) {
82            $fmainaddress = $self->object->id . '-' . join('', map { ('a'..'z')[rand(26)] }
83                (0..4));
84            $self->base->_create_c_object(
85                'address', $fmainaddress,
86                user => $self->object->id,
87                isMainAddress => 1, ) or do {
88                $self->base->log(LA_ERR,
89                    "Cannot create main address for user %s", $self->object->id);
90                return;
91            };
92        }
93        if ($fmainaddress && 
94            (my $address = $self->base->get_object('address', $fmainaddress))) {
95            if ($address->attribute($self->name) &&
96                !$address->attribute($self->name)->ro) {
97                return $address->set_c_fields($self->name => $data) ||0;
98            }
99        }
100    };
101
102    my $attrs = {
103            exported => {
104                post => sub {
105                    my ($self, $value) = @_;
106                    my $attr = $self->name;
107
108                    if (my $obj = $self->base->
109                            get_object('revaliases', $self->object->id)) {
110                        my $ares = $obj->set_c_fields(
111                            ($attr eq 'exported' ? 'exported' : 'unexported') => $value
112                        );
113                        if (!defined($ares)) {
114                            $self->base->log(LA_ERR,
115                                'Cannot set revaliases exported attribute for user %s',
116                                $self->object->id);
117                        }
118                    }
119                    my $must_expire = $self->name eq 'exported'
120                        ? ($value ? 0 : 1 )
121                        : ($value ? 1 : 0 );
122
123                    foreach my $al (grep { $_ } $self->object->get_attributes('aliases'), $self->object->id) {
124                        my $obj = $self->base->get_object('aliases', $al) or next;
125                        $obj->_set_c_fields(
126                            exported => $value,
127                        );
128                    }
129                    foreach my $al ($self->object->get_attributes('_otheraddress')) {
130                        my $obj = $self->base->get_object('address', $al) or next;
131                        $obj->_set_c_fields(
132                            exported => $value,
133                        );
134                    }
135                },
136            },
137            uidNumber => {
138                inline => 1,
139                iname => 'uidnumber',
140                uniq => 1,
141                mandatory => 1,
142                formopts => { length => 7 },
143                label => l('UID'),
144            },
145            uidnumber => { inline => 1, hide => 1, monitored => 1 },
146            gidNumber => {
147                inline => 1,
148                iname => 'gidnumber',
149                mandatory => 1,
150                can_values => sub {
151                    map { $_->id, $_->get_attributes('gidNumber') }
152                    map { $base->get_object('group', $_) }
153                    $base->list_objects('group')
154                },
155                can_values => sub {
156                    my $sth = $base->db->prepare_cached(
157                        q{
158                        select name, gidnumber from "group" where exported = true
159                        }
160                    );
161                    $sth->execute();
162                    my @res;
163                    while (my $res = $sth->fetchrow_hashref) {
164                        push(@res, $res->{name});
165                        push(@res, $res->{gidnumber});
166                    }
167                    return @res;
168                },
169                display => sub {
170                    my ($self, $val) = @_;
171                    if ($val =~ /^\d+$/) {
172                        my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
173                            or return;
174                        return $gr;
175                    } else {
176                        my ($gr) = $self->base->search_objects('group', "name=$val")
177                            or return;
178                        return $gr;
179                    }
180                },
181                input => sub {
182                    my ($val) = @_;
183                    $val =~ /^\d+$/ and return $val;
184                    my ($gr) = $base->search_objects('group', "name=$val") or return;
185                    return $base->get_object('group', $gr)->get_attributes('gidNumber');
186                },
187                #reference => 'group',
188                label => l('GID'),
189            },
190            loginShell => {
191                mandatory => 1,
192                label => l('Shell'),
193            },
194            gidnumber => { inline => 1, hide => 1,
195                can_values => sub {
196                    map { $_->id, $_->get_attributes('gidNumber') }
197                    map { $base->get_object('group', $_) }
198                    $base->list_objects('group')
199                },
200                display => sub {
201                    my ($self, $val) = @_;
202                    my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
203                        or return;
204                    return $gr;
205                },
206                input => sub {
207                    my ($val) = @_;
208                    $val =~ /^\d+$/ and return $val;
209                    my ($gr) = $base->search_objects('group', "name=$val") or return;
210                    return $base->get_object('group', $gr)->get_attributes('gidNumber');
211                },
212                mandatory => 1,
213                reference => 'group',
214                monitored => 1,
215            },
216            locked    => {
217                formtype => 'CHECKBOX',
218                formopts => { rawvalue => 1, },
219                monitored => 1,
220                label => l('Locked'),
221            },
222            expire        => {
223                inline => 1,
224                formtype => 'DATETIME',
225                monitored => 1,
226                label => l('Expire on'),
227            },
228            endcircuit    => {
229                inline => 1,
230                formtype => 'DATE',
231                monitored => 1,
232                label => l('End of entrance'),
233            },
234            _endEmployment => {
235                formtype => 'DATETIME',
236                managed => 1,
237                ro => 1,
238                hide => 1,
239                get => sub {
240                    my ($attr) = @_;
241                    my $self = $attr->object;
242                    $self->_computeEndEmployment($self->base->config('employment_delay') || 0);
243                },
244                label => l('End of employment'),
245            },
246            endEmployment => {
247                formtype => 'DATETIME',
248                ro => 1,
249                label => l('End of employment'),
250            },
251            _departure => {
252                formtype => 'DATETIME',
253                managed => 1,
254                ro => 1,
255                hide => 1,
256                get => sub {
257                    my ($attr) = @_;
258                    my $self = $attr->object;
259                    $self->_computeEndEmployment($self->base->config('employment_delay') || 0, 1, 1);
260                },
261                label => l('Start of employment'),
262            },
263            departure => {
264                formtype => 'DATETIME',
265                ro => 1,
266                label => l('Leaving'),
267            },
268            _endStrictEmployment => {
269                formtype => 'DATETIME',
270                managed => 1,
271                ro => 1,
272                hide => 1,
273                get => sub {
274                    my ($attr) = @_;
275                    my $self = $attr->object;
276                    $self->_computeEndEmployment();
277                },
278            },
279            endStrictEmployment => {
280                formtype => 'DATETIME',
281                ro => 1,
282            },
283            _endCurrentEmployment => {
284                formtype => 'DATETIME',
285                managed => 1,
286                ro => 1,
287                hide => 1,
288                get => sub {
289                    my ($attr) = @_;
290                    my $self = $attr->object;
291
292                    my $list_empl = $self->base->db->prepare_cached(q{
293                        SELECT * FROM employment WHERE "user" = ? and
294                            firstday <= now() and
295                            (lastday is null or lastday >= now() - '1 days'::interval)
296                            order by firstday asc
297                    });
298                    $list_empl->execute($self->id);
299                    my $end;
300                    while (my $res = $list_empl->fetchrow_hashref) {
301                        if (!$res->{lastday}) {
302                            # Ultimate employment.
303                            $list_empl->finish;
304                            return undef;
305                        } else {
306                            $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
307                            $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
308                            $end->add(hours => 23, minutes => 59, seconds => 59);
309                        }
310                        last;
311                    }
312                    $list_empl->finish;
313
314                    return $end ? $end->iso8601 : undef
315                },
316            },
317            endCurrentEmployment => {
318                formtype => 'DATETIME',
319                ro => 1,
320            },
321            _endLastEmployment => {
322                formtype => 'DATETIME',
323                managed => 1,
324                ro => 1,
325                hide => 1,
326                get => sub {
327                    my ($attr) = @_;
328                    my $self = $attr->object;
329
330                    my $list_empl = $self->base->db->prepare_cached(q{
331                        SELECT * FROM employment WHERE "user" = ?
332                        order by lastday desc nulls first
333                    });
334                    $list_empl->execute($self->id);
335                    my $res = $list_empl->fetchrow_hashref;
336                    $list_empl->finish;
337                    my $end;
338                    if ($res && $res->{lastday}) {
339                        $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
340                        $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
341                        $end->add(hours => 23, minutes => 59, seconds => 59);
342                    }
343                    return $end ? $end->iso8601 : undef
344                },
345                label => l('End of any employment'),
346            },
347            endLastEmployment => {
348                formtype => 'DATETIME',
349                ro => 1,
350                label => l('End of any employment'),
351            },
352            _startEmployment => {
353                formtype => 'DATETIME',
354                managed => 1,
355                ro => 1,
356                hide => 1,
357                get => sub {
358                    my ($attr) = @_;
359                    my $self = $attr->object;
360                    $self->_computeStartEmployment($self->base->config('employment_delay') || 0);
361                },
362                label => l('Start of employment'),
363            },
364            startEmployment => {
365                formtype => 'DATETIME',
366                ro => 1,
367                label => l('Start of employment'),
368            },
369            _arrival => {
370                formtype => 'DATETIME',
371                managed => 1,
372                ro => 1,
373                hide => 1,
374                get => sub {
375                    my ($attr) = @_;
376                    my $self = $attr->object;
377                    $self->_computeStartEmployment($self->base->config('employment_delay') || 0, 1, 1);
378                },
379                label => l('Start of employment'),
380            },
381            arrival => {
382                formtype => 'DATETIME',
383                ro => 1,
384                label => l('Arrival'),
385            },           
386            _startStrictEmployment => {
387                formtype => 'DATETIME',
388                managed => 1,
389                ro => 1,
390                hide => 1,
391                get => sub {
392                    my ($attr) = @_;
393                    my $self = $attr->object;
394                    $self->_computeStartEmployment();
395                },
396            },
397            startStrictEmployment => {
398                formtype => 'DATETIME',
399                ro => 1,
400            },
401            _startCurrentEmployment => {
402                formtype => 'DATETIME',
403                managed => 1,
404                ro => 1,
405                hide => 1,
406                get => sub {
407                    my ($attr) = @_;
408                    my $self = $attr->object;
409
410                    my $list_empl = $self->base->db->prepare_cached(q{
411                        SELECT * FROM employment WHERE "user" = ? and
412                            firstday <= now() and
413                            (lastday is null or lastday >= now() - '1 days'::interval)
414                            order by firstday asc
415                    });
416                    $list_empl->execute($self->id);
417                    my $start;
418                    while (my $res = $list_empl->fetchrow_hashref) {
419                        $start = DateTime->from_epoch(epoch => str2time($res->{firstday}));
420                        $start->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
421                        last;
422                    }
423                    $list_empl->finish;
424
425                    return $start ? $start->iso8601 : undef
426                },
427            },
428            startCurrentEmployment => {
429                formtype => 'DATETIME',
430                ro => 1,
431            },
432            _startFirstEmployment => {
433                formtype => 'DATETIME',
434                managed => 1,
435                ro => 1,
436                hide => 1,
437                get => sub {
438                    my ($attr) = @_;
439                    my $self = $attr->object;
440
441                    my $list_empl = $self->base->db->prepare_cached(q{
442                        SELECT * FROM employment WHERE "user" = ?
443                        order by firstday asc
444                    });
445                    $list_empl->execute($self->id);
446                    my $res = $list_empl->fetchrow_hashref;
447                    $list_empl->finish;
448                    my $start;
449                    if ($res && $res->{firstday}) {
450                        $start = DateTime->from_epoch(epoch => str2time($res->{firstday}));
451                        $start->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
452                    }
453                    return $start ? $start->iso8601 : undef
454                },
455                label => l('Start of any employment'),
456            },
457            startFirstEmployment => {
458                formtype => 'DATETIME',
459                ro => 1,
460                label => l('Start of any employment'),
461            },
462            employmentLength => {
463                ro => 1,
464                auto => 1,
465            },
466            _employmentLength => {
467                ro => 1,
468                managed => 1,
469                hide => 1,
470                get => sub {
471                    my ($self) = @_;
472                    my $lastday = $self->object->get_attributes('endEmployment') || DateTime->now->ymd('-');
473                    my $firstday = $self->object->get_attributes('startEmployment') or return;
474
475                    my @fd = split('-', DateTime->from_epoch(epoch => str2time($firstday))->ymd('-'));
476                    my @ld = split('-', DateTime->from_epoch(epoch => str2time($lastday))->ymd('-'));
477
478                    return Date::Calc::Delta_Days(@fd, @ld) +1;
479                },
480                label => l('Work duration'),
481            },
482            employmentLengthText => {
483                ro => 1,
484                auto => 1,
485            },
486            _employmentLengthText => {
487                ro => 1,
488                managed => 1,
489                hide => 1,
490                get => sub {
491                    my ($self) = @_;
492                    my $firstday = $self->object->get_attributes('startEmployment') or return;
493                    my $lastday = $self->object->get_attributes('endEmployment')|| DateTime->now->ymd('-');
494                    {
495                        my $dtlast = DateTime->from_epoch(epoch => str2time($lastday));
496                        $dtlast->add(days => 1);
497                        $lastday = $dtlast->ymd('-');
498                    }
499
500                    my @fd = split('-', DateTime->from_epoch(epoch => str2time($firstday))->ymd('-'));
501                    my @ld = split('-', DateTime->from_epoch(epoch => str2time($lastday))->ymd('-'));
502
503                    my ($Dy,$Dm,$Dd) = Date::Calc::N_Delta_YMD(@fd, @ld);
504                    return join(', ',
505                        ($Dy ? l('%d years', $Dy)  : ()),
506                        ($Dm ? l('%d months', $Dm) : ()),
507                        ($Dd ? l('%d days', $Dd)   : ()),
508                    );
509                },
510                label => l('Work duration'),
511            },
512            cn        => {
513                inline => 1, ro => 1,
514                get => sub {
515                    my ($self) = @_;
516                    return join(' ', grep { $_ } 
517                        (
518                            $self->object->_get_c_field('givenName'),
519                            $self->object->_get_c_field('sn')
520                        )
521                    )
522                    || $self->object->_get_c_field('description')
523                    || $self->object->id;
524                },
525            },
526            memberOf  => {
527                reference => 'group',
528                multiple => 1, delayed => 1,
529                get => sub {
530                    my ($self) = @_;
531                    my $obj = $self->object;
532                    my $sth = $obj->db->prepare_cached(
533                        q{
534                        select name from "group" join
535                        group_attributes on group_attributes.okey = "group".ikey
536                        where value = ? and attr = ? and internobject = false
537                        } . ($self->base->{wexported} ? '' : ' and "group".exported = true')
538                    );
539                    $sth->execute($obj->id, 'memberUID');
540                    my @res;
541                    while (my $res = $sth->fetchrow_hashref) {
542                        push(@res, $res->{name});
543                    }
544                    return \@res;
545                },
546                set => sub {
547                    my ($self, $values) = @_;
548                    my %old = map { $_ => 'o' } @{ $self->get };
549                    foreach my $group (grep { $_ } ref $values ? @{ $values } : $values) {
550                        if ($old{$group}) {
551                            $old{$group} = undef; # no change
552                        } else {
553                            $old{$group} = 'n';
554                        }
555                    }
556
557                    my $res = 0;
558                    foreach my $group (keys %old) {
559                        $old{$group} or next; # no change
560
561                        my $ogroup = $self->base->get_object('group', $group) or next;
562                        ($ogroup->_get_c_field('sutype') || '') =~ /^(jobtype|contrattype)$/ and next;
563
564                        if ($old{$group} eq 'n') {
565                            $res += $ogroup->_addAttributeValue('memberUID', $self->object->id);
566                        } else {
567                            if (($self->object->_get_c_field('department') || '') eq $ogroup->id) {
568                                $self->base->log(LA_WARN,
569                                    "Don't removing user %s from group %s: is its department",
570                                    $self->object->id, $ogroup->id);
571                                next;
572                            }
573                            $res += $ogroup->_delAttributeValue('memberUID', $self->object->id);
574                        }
575                    }
576                    return $res;
577                },
578                label => l('Member of'),
579            },
580            forward => {
581                managed => 1,
582                multiple => 1,
583                get => sub {
584                    my ($self) = @_;
585                    my $sth = $self->base->db->prepare(q{
586                        select forward from aliases where name = ? and internobject = false
587                        } . ($self->base->{wexported} ? '' : ' and exported = true'));
588                    $sth->execute($self->object->id);
589                    my $res = $sth->fetchrow_hashref;
590                    $sth->finish;
591                    return $res->{forward}
592                },
593                set => sub {
594                    my ($self, $data) = @_;
595                    if ($data) {
596                        my @datas = ref $data ? @$data : split(/\s*,\s*/, $data);
597                        if (my $f = $self->base->get_object('aliases', $self->object->id)) {
598                            return $f->_set_c_fields(
599                                forward => \@datas,
600                                comment => undef,
601                                description => 'Forward for user ' . $self->object->id,
602                            );
603                        } else {
604                            if ($self->base->_create_c_object(
605                                    'aliases', $self->object->id,
606                                    forward => \@datas,
607                                    description => 'automatically created for ' . $self->object->id,
608                                )) {
609                                return 1;
610                            } else {
611                                $self->base->log(LA_ERR, "Cannot add forward for %s",
612                                    $self->object->id);
613                            }
614                        }
615                    } elsif ($self->base->get_object('aliases', $self->object->id)) {
616                        if (my $res = $self->base->_delete_object('aliases', $self->object->id)) {
617                            return $res;
618                        } else {
619                            $self->base->log(LA_ERR, "Cannot remove forward for %s",
620                                $self->object->id);
621                        }
622                    } else {
623                        return 1;
624                    }
625                    return;
626                },
627                label => l('Forward'),
628            },
629            aliases   => {
630                #reference => 'aliases',
631                delayed => 1,
632                formtype => 'TEXT',
633                multiple => 1,
634                get => sub {
635                    my ($self) = @_;
636                    my $sth = $self->base->db->prepare(q{
637                        select name from aliases where internobject = false and lower($1) =
638                        lower(array_to_string("forward", ','))
639                        } . ($self->base->{wexported} ? '' : 'and exported = true'));
640                    $sth->execute($self->object->id);
641                    my @values;
642                    while (my $res = $sth->fetchrow_hashref) {
643                        push(@values, $res->{name});
644                    }
645                    return \@values;
646                },
647                set => sub {
648                    my ($self, $data) = @_;
649
650                    my $res = 0;
651                    my %aliases = map { $_ => 1 } grep { $_ } (ref $data ? @{$data} : $data);
652                    foreach ($self->object->_get_attributes('aliases')) {
653                        $aliases{$_} ||= 0;
654                        $aliases{$_} +=2;
655                    }
656                    foreach (keys %aliases) {
657                        if ($aliases{$_} == 2) {
658                            if ($self->base->_delete_object('aliases', $_)) {
659                                $res++
660                            } else {
661                                $self->base->log(LA_ERR,
662                                    "Cannot remove aliases %s from user %s", $_,
663                                    $self->object->id);
664                            }
665                        } elsif ($aliases{$_} == 1) {
666                            if ($self->base->_create_c_object(
667                                    'aliases', $_,
668                                    forward => [ $self->object->id ],
669                                    description => 'automatically created for ' . $self->object->id,
670                                )) {
671                                $res++
672                            } else {
673                                $self->base->log(LA_ERR, 'Cannot set forward %s to user %s',
674                                    $_, $self->object->id);
675                                return;
676                            }
677                        } # 3 no change
678                    }
679                    $res
680                },
681                label => l('Aliases'),
682            },
683            revaliases => {
684                formtype => 'TEXT',
685                get => sub {
686                    my ($self) = @_;
687
688                    if (my $obj = $self->base->
689                        get_object('revaliases', $self->object->id)) {
690                        return $obj->get_attributes('as');
691                    } else {
692                        return;
693                    }
694                },
695                set => sub {
696                    my ($self, $data) = @_;
697               
698                    my $res = 0;
699                    if ($data) {
700                        if (my $obj = $self->base->
701                            get_object('revaliases', $self->object->id)) {
702                            my $ares = $obj->set_c_fields(
703                                'as' => $data,
704                                'exported' => ($self->object->get_attributes('exported') || 0),
705                            );
706                            if (defined($ares)) {
707                                $res+=$ares;
708                            } else {
709                                $self->base->log(LA_ERR, 'Cannot set revaliases for user %s',
710                                    $self->object->id);
711                            }
712                        } else {
713                            if ($self->base->_create_c_object(
714                                    'revaliases',
715                                    $self->object->id, as => $data,
716                                    'exported' => ($self->object->get_attributes('exported') || 0),
717                                    description => 'automatically created for ' . $self->object->id,
718                                )) {
719                                $res++;
720                            } else {
721                                $self->base->log(LA_ERR, 'Cannot set revaliases for user %s',
722                                    $self->object->id);
723                            }
724                        }
725                    } else {
726                        $self->base->_delete_object('revaliases', $self->object->id);
727                        $res++;
728                    }
729
730                    $res
731                },
732            },
733            manager => {
734                reference => 'user',
735                ro => 1,
736                get => sub {
737                    my ($self) = @_;
738                    if (my $manager = $self->object->_get_c_field('managerContact')) {
739                        return $manager;
740                    } elsif (my $department = $self->object->_get_c_field('department')) {
741                        my $obj = $self->base->get_object('group', $department);
742                        return $obj->_get_c_field('managedBy');
743                    } else {
744                        return;
745                    }
746                },
747                label => l('Responsible'),
748            },
749            department => {
750                reference => 'group',
751                can_values => sub {
752                    $base->search_objects('group', 'sutype=dpmt')
753                },
754                monitored => 1,
755                label => l('Department'),
756            },
757            contratType => {
758                reference => 'group',
759                can_values => sub {
760                    $base->search_objects('group', 'sutype=contrattype')
761                },
762                monitored => 1,
763                label => l('Type of contract'),
764            },
765            site => {
766                reference => 'site',
767                can_values => sub {
768                    $base->search_objects('site')
769                },
770                get => sub {
771                    my ($self) = @_;
772                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
773                        $self->base->get_object('address', $fmainaddress)
774                            ->_get_c_field($self->name);
775                    } else {
776                        return;
777                    }
778                },
779                set => $subsetaddress,
780                label => l('Site'),
781            },
782            co => {
783                get => sub {
784                    my ($self) = @_;
785                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
786                        $self->base->get_object('address', $fmainaddress)
787                            ->_get_c_field($self->name);
788                    } else {
789                        return;
790                    }
791                },
792                set => $subsetaddress,
793            },
794            l => {
795                get => sub {
796                    my ($self) = @_;
797                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
798                        $self->base->get_object('address', $fmainaddress)
799                            ->_get_c_field($self->name);
800                    } else {
801                        return;
802                    }
803                },
804                set => $subsetaddress,
805                label => l('City'),
806            },
807            postalCode => {
808                get => sub {
809                    my ($self) = @_;
810                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
811                        $self->base->get_object('address', $fmainaddress)
812                            ->_get_c_field($self->name);
813                    } else {
814                        return;
815                    }
816                },
817                set => $subsetaddress,
818                label => l('Postal code'),
819            },
820            streetAddress => {
821                formtype => 'TEXTAREA',
822                get => sub {
823                    my ($self) = @_;
824                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
825                        $self->base->get_object('address', $fmainaddress)
826                            ->_get_c_field($self->name);
827                    } else {
828                        return;
829                    }
830                },
831                set => $subsetaddress,
832                label => l('Street'),
833            },
834            postOfficeBox => {
835                get => sub {
836                    my ($self) = @_;
837                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
838                        $self->base->get_object('address', $fmainaddress)
839                            ->_get_c_field($self->name);
840                    } else {
841                        return;
842                    }
843                },
844                set => $subsetaddress,
845                label => l('Post office box'),
846            },
847            st => {
848                get => sub {
849                    my ($self) = @_;
850                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
851                        $self->base->get_object('address', $fmainaddress)
852                            ->_get_c_field($self->name);
853                    } else {
854                        return;
855                    }
856                },
857                set => $subsetaddress,
858            },
859            facsimileTelephoneNumber => {
860                get => sub {
861                    my ($self) = @_;
862                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
863                        $self->base->get_object('address', $fmainaddress)
864                            ->_get_c_field($self->name);
865                    } else {
866                        return;
867                    }
868                },
869                set => $subsetaddress,
870                label => l('Fax number'),
871            },
872            o => {
873                ro => 1,
874                iname => 'company',
875                label => l('Company'),
876            },
877            ou => {
878                iname => 'department',
879                ro => 1,
880                label => l('Department'),
881            },
882            telephoneNumber => {
883                get => sub {
884                    my ($self) = @_;
885                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
886                        $self->base->get_object('address', $fmainaddress)
887                            ->_get_c_field($self->name);
888                    } else {
889                        return;
890                    }
891                },
892                set => $subsetaddress,
893                label => l('Phone number'),
894            },
895            physicalDeliveryOfficeName => {
896                get => sub {
897                    my ($self) = @_;
898                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
899                        $self->base->get_object('address', $fmainaddress)
900                            ->_get_c_field($self->name);
901                    } else {
902                        return;
903                    }
904                },
905                set => $subsetaddress,
906                label => l('Office'),
907            },
908            uid => {
909                iname => 'name',
910                ro => 1,
911                label => l('Login'),
912            },
913            cn =>  { iname => 'name', ro => 1 },
914            gecos => {
915                ro => 1,
916                auto => 1,
917            },
918            _gecos => {
919                managed => 1,
920                hide => 1,
921                ro => 1,
922                get => sub {
923                    my ($self) = @_;
924                    my $obj = $self->object;
925
926                    my $fmt = $self->base->config('gecosformat') ||
927                        '%{_displayName},%{l}-%{physicalDeliveryOfficeName},%{telephoneNumber},%{?endcircuit:%{endcircuit}}%{?!endcircuit:%{expireText}}';
928                    my $gecos = $obj->queryformat($fmt);
929                    return to_ascii($gecos);
930                },
931                label => l('GECOS'),
932            },
933            displayName => {
934                ro => 1,
935                auto => 1,
936            },
937            _displayName  => {
938                ro => 1, managed => 1,
939                hide => 1,
940                get => sub {
941                    my ($self) = @_;
942                    return join(' ', grep { $_ } 
943                        (
944                            $self->object->_get_c_field('givenName'),
945                            $self->object->_get_c_field('sn')
946                        )
947                    )
948                    || $self->object->_get_c_field('description')
949                    || $self->object->id;
950                },
951                label => l('Name'),
952            },
953            sAMAccountName => {
954                auto => 1,
955            },
956            _sAMAccountName  => {
957                ro => 1,
958                hide => 1,
959                managed => 1,
960                iname => 'name',
961                get => sub {
962                    my ($self) = @_;
963                    substr($self->object->id, 0, 19)
964                },
965            },
966            accountExpires => {
967                ro => 1,
968                auto => 1,
969            },
970            _accountExpires => {
971                ro => 1,
972                managed => 1,
973                hide => 1,
974                get => sub {
975                    my ($self) = @_;
976                    my $obj = $self->object;
977                    my $sth = $obj->db->prepare_cached(
978                        sprintf(
979                            q{select extract(epoch from %s) + 11644474161 as expire
980                            from %s where %s = ?},
981                            $base->config('endCircuitdontExpire') ? 'expire' : 'COALESCE(endcircuit,  expire)',
982                            $obj->db->quote_identifier($obj->_object_table),
983                            $obj->db->quote_identifier($obj->_key_field),
984                        )
985                    );
986                    $sth->execute($obj->id);
987                    my $res = $sth->fetchrow_hashref;
988                    $sth->finish;
989                    return $res->{expire} ? sprintf("%.f", $res->{expire} * 1E7) : '9223372036854775807';
990                }
991            },
992            shadowExpire => {
993                ro => 1,
994                auto => 1,
995            },
996            _shadowExpire => {
997                ro => 1,
998                hide => 1,
999                managed => 1,
1000                get => sub {
1001                    my ($self) = @_;
1002                    my $obj = $self->object;
1003                    my $sth = $obj->db->prepare_cached(
1004                        sprintf(
1005                            q{select justify_hours(%s - '1/1/1970'::timestamp) as expire
1006                            from %s where %s = ?},
1007                            $base->config('endCircuitdontExpire') ? 'expire' : 'COALESCE(endcircuit,  expire)',
1008                            $obj->db->quote_identifier($obj->_object_table),
1009                            $obj->db->quote_identifier($obj->_key_field),
1010                        )
1011                    );
1012                    $sth->execute($obj->id);
1013                    my $res = $sth->fetchrow_hashref;
1014                    $sth->finish;
1015                    return -1 unless($res->{expire});
1016                    $res->{expire} =~ /(\d+) days\s*(\w)?/;
1017                    # Add one day is time is not 00H00
1018                    return $1 + ($2 ? 1 : 0);
1019                }
1020            },
1021            directReports => {
1022                auto => 1,
1023                ro => 1,
1024            },
1025            _directReports => {
1026                reference => 'user',
1027                ro => 1,
1028                hide => 1,
1029                delayed => 1,
1030                get => sub {
1031                    my ($self) = @_;
1032                    my $obj = $self->object;
1033                    my $sth = $obj->db->prepare_cached(
1034                        q{
1035                        SELECT
1036                        "user".name FROM
1037                        public."user",
1038                        public.user_attributes_groups,
1039                        public.group_attributes_users,
1040                        public.group_attributes_base gb,
1041                        public."group"
1042                        WHERE
1043                        "user".ikey = user_attributes_groups.okey AND
1044                        user_attributes_groups.value = "group".name AND
1045                        group_attributes_users.okey = gb.okey AND
1046                        "group".ikey = group_attributes_users.okey AND
1047                        gb.attr = 'sutype' AND
1048                        gb.value = 'dpmt' AND
1049                        group_attributes_users.attr = 'managedBy' AND
1050                        group_attributes_users.value = ?
1051                        } . ($self->base->{wexported} ? '' : ' and "user".exported = true') . q{
1052                            and "user".ikey not in
1053                        (select okey from user_attributes_users
1054                        where attr = 'manager' and value != ? )
1055                        union
1056                        select "user".name FROM public."user",
1057                        user_attributes_users where
1058                        user_attributes_users.attr = 'manager' and user_attributes_users.value = ?
1059                            and "user".ikey = user_attributes_users.okey
1060                        } . ($self->base->{wexported} ? '' : ' and "user".exported = true')
1061                    );
1062                    $sth->execute($obj->id, $obj->id, $obj->id);
1063                    my @res;
1064                    while (my $res = $sth->fetchrow_hashref) {
1065                        push(@res, $res->{name});
1066                    }
1067                    return \@res;
1068                },
1069            },
1070            managedObjects => { ro => 1, reference => 'group', },
1071            otheraddress => {
1072                auto => 1,
1073                multiple => 1,
1074                reference => 'address',
1075            },
1076            _otheraddress => {
1077                ro => 1,
1078                multiple => 1,
1079                reference => 'address',
1080                get => sub {
1081                    my ($self) = @_;
1082                    my $sth = $self->base->db->prepare_cached(q{
1083                        select name from address left join address_attributes
1084                        on address.ikey = address_attributes.okey and
1085                        address_attributes.attr = 'isMainAddress'
1086                        where "user" = ?
1087                        } . ($self->base->{wexported} ? '' : ' and "address".exported = true') . q{
1088                        order by address_attributes.attr
1089                        } );
1090                    $sth->execute($self->object->id);
1091                    my @values;
1092                    while (my $res = $sth->fetchrow_hashref) {
1093                        push(@values, $res->{name});
1094                    }
1095                    return \@values;
1096                },
1097            },
1098            mainaddress => {
1099                auto => 1,
1100                reference => 'address',
1101            },
1102            _mainaddress => {
1103                hide => 1,
1104                ro => 1,
1105                reference => 'address',
1106                get => sub {
1107                    my ($self) = @_;
1108                    my $sth = $self->base->db->prepare_cached(q{
1109                        select name from address join address_attributes on ikey = okey
1110                        where "user" = ? and attr = 'isMainAddress'
1111                        } . ($self->base->{wexported} ? '' : ' and "address".exported = true'));
1112                    $sth->execute($self->object->id);
1113                    my $res = $sth->fetchrow_hashref;
1114                    $sth->finish;
1115                    return $res->{name};
1116                },
1117            },
1118            postalAddress => {
1119                auto => 1,
1120            },
1121            _postalAddress => {
1122                hide => 1,
1123                ro => 1,
1124                get => sub {
1125                    my ($self) = @_;
1126                    if (my $fmainaddress = $self->object->_get_c_field('_mainaddress')) {
1127                        $self->base->get_object('address', $fmainaddress)
1128                            ->_get_c_field('postalAddress');
1129                    } else {
1130                        return;
1131                    }
1132                },
1133                label => l('Postal Address'),
1134            },
1135            facsimileTelephoneNumber => {
1136                ro => 1,
1137                label => l('Fax number'),
1138            },
1139            allsite   => {
1140                ro => 1,
1141                reference => 'site',
1142            },
1143            managerContact => {
1144                delayed => 1,
1145                can_values => sub {
1146                    my %uniq = map { $_ => 1 } grep { $_ }
1147                    (($_[1] ? $_[1]->get_attributes('managerContact') : ()),
1148                    $base->search_objects('user', 'active=1'));
1149                    sort keys %uniq;
1150                },
1151                reference => 'user',
1152                monitored => 1,
1153                iname => 'manager',
1154                label => l('Manager'),
1155            },
1156            expireTextEC => {
1157                ro => 1,
1158                auto => 1,
1159            },
1160            _expireTextEC => {
1161                ro => 1,
1162                managed => 1,
1163                hide => 1,
1164                get => sub {
1165                    my ($self) = @_;
1166                    my $obj = $self->object;
1167                    my $sth = $obj->db->prepare_cached(
1168                        sprintf(
1169                            q{select to_char(%s, 'YYYY/MM/DD') as expire
1170                            from %s where %s = ?},
1171                            $base->config('endCircuitdontExpire') ? 'expire' : 'COALESCE(endcircuit,  expire)',
1172                            $obj->db->quote_identifier($obj->_object_table),
1173                            $obj->db->quote_identifier($obj->_key_field),
1174                        )
1175                    );
1176                    $sth->execute($obj->id) or $obj->db->rollback;
1177                    my $res = $sth->fetchrow_hashref;
1178                    $sth->finish;
1179                    return $res->{expire}
1180                },
1181            },
1182            expireText => {
1183                ro => 1,
1184                auto => 1,
1185            },
1186            _expireText => {
1187                ro => 1,
1188                hide => 1,
1189                managed => 1,
1190                get => sub {
1191                    my ($self) = @_;
1192                    my $obj = $self->object;
1193                    my $sth = $obj->db->prepare_cached(
1194                        sprintf(
1195                            q{select to_char(expire, 'YYYY/MM/DD') as expire
1196                            from %s where %s = ?},
1197                            $obj->db->quote_identifier($obj->_object_table),
1198                            $obj->db->quote_identifier($obj->_key_field),
1199                        )
1200                    );
1201                    $sth->execute($obj->id) or $obj->db->rollback;
1202                    my $res = $sth->fetchrow_hashref;
1203                    $sth->finish;
1204                    return $res->{expire}
1205                },
1206            },
1207            krb5ValidEnd => {
1208                ro => 1,
1209                auto => 1,
1210            },
1211            _krb5ValidEnd => {
1212                ro => 1,
1213                managed => 1,
1214                hide => 1,
1215                get => sub {
1216                    my ($self) = @_;
1217                    my $sth = $self->object->db->prepare_cached(
1218                        sprintf(
1219                            q{select date_part('epoch', COALESCE(endcircuit,  expire))::int as expire
1220                            from %s where %s = ?},
1221                            $self->object->db->quote_identifier($self->object->_object_table),
1222                            $self->object->db->quote_identifier($self->object->_key_field),
1223                        )
1224                    );
1225                    $sth->execute($self->object->id) or $self->object->db->rollback;
1226                    my $res = $sth->fetchrow_hashref;
1227                    $sth->finish;
1228                    return $res->{expire}
1229                },
1230            },
1231            cells  => {
1232                ro => 1,
1233                reference => 'group',
1234            },
1235            departments => {
1236                reference => 'group',
1237                delayed => 1,
1238                ro => 1,
1239                label => l('Departments'),
1240            },
1241            arrivalDate => { },
1242            expired => {
1243                ro => 1,
1244                label => l('Expired'),
1245            },
1246            active => {
1247                ro => 1,
1248                label => l('Active'),
1249            },
1250            status => {
1251                ro => 1,
1252                label => l('Statut du compte'),
1253            },
1254            pwdAccountLockedTime => {
1255                auto => 1,
1256                ro => 1,
1257            },
1258            _pwdAccountLockedTime => {
1259                managed => 1,
1260                ro => 1,
1261                hide => 1,
1262                get => sub {
1263                    my ($self) = @_;
1264                    my $obj = $self->object;
1265                    if ($obj->_get_c_field('locked')) {
1266                        return '000001010000Z';
1267                    } else {
1268                        my $sth = $obj->db->prepare_cached(
1269                            sprintf(
1270                                q{select to_char(%s AT TIME ZONE 'Z', 'YYYYMMDDHH24MISSZ') as expire
1271                                from %s where %s = ? and expire < now()},
1272                                $base->config('endCircuitdontExpire') ? 'expire' : 'COALESCE(endcircuit,  expire)',
1273                                $obj->db->quote_identifier($obj->_object_table),
1274                                $obj->db->quote_identifier($obj->_key_field),
1275                            )
1276                        );
1277                        $sth->execute($obj->id);
1278                        my $res = $sth->fetchrow_hashref;
1279                        $sth->finish;
1280                        return $res->{expire}
1281                    }
1282                },
1283            },
1284            userPassword => { readable => 0, },
1285            wWWHomePage => {
1286                label => l('Web Page'),
1287                formopts => { length => 35 },
1288            },
1289            title => { },
1290            snNative => {
1291                label => l('Native name'),
1292            },
1293            givenNameNative => {
1294                label => l('Native first name'),
1295            },
1296            sn => {
1297                label => l('Name'),
1298            },
1299            shadowWarning => { },
1300            shadowMin => { },
1301            shadowMax => { },
1302            shadowLastChange => { },
1303            shadowInactive => { },
1304            shadowFlag => { },
1305            otherTelephone => { },
1306            nickname => {
1307                label => l('Nickname'),
1308            },
1309            mobile => { },
1310            mail => {
1311                label => l('Email'),
1312            },
1313            otherEmail => {
1314                label => l('External mail'),
1315            },
1316            labeledURI => { },
1317            jobType => { },
1318            ipPhone => { },
1319            initials => {
1320                label => l('Initials'),
1321                checkinput => sub {
1322                    $_[0] or return;
1323                    return(length($_[0]) <= 6)
1324                }
1325            },
1326            homePhone => { },
1327            homeDirectory => {
1328                label => l('Home directory'),
1329            },
1330            halReference => {
1331                label => l('HAL id'),
1332            },
1333            grade => { },
1334            givenName => {
1335                label => l('First name'),
1336            },
1337            encryptedPassword => { },
1338            description => {
1339                label => l('Description'),
1340            },
1341            company => {
1342                label => l('Company'),
1343            },
1344            employer => {
1345                label => l('Employer'),
1346            },
1347            comment => {
1348                label => l('Comment'),
1349            },
1350            college => { },
1351            passwordLastSet => {
1352                ro => 1,
1353                label => l('Password set'),
1354            },
1355            oldPassword => {
1356                multiple => 1,
1357            },
1358            bannedPassword => {
1359                multiple => 1,
1360            },
1361            sshPublicKey => {
1362                multiple => 1,
1363                formopts => { length => 45 },
1364            },
1365            sshPublicKeyFilter => {
1366                multiple => 1,
1367                formopts => { length => 45 },
1368            },
1369            delUnknownSshKey => {
1370                formtype => 'CHECKBOX',
1371            },
1372            _authorizedKeys => {
1373                multiple => 1,
1374                ro => 1,
1375                managed => 1,
1376                hide => 1,
1377                get => sub {
1378                    my ($attr) = @_;
1379                    my $self = $attr->object;
1380
1381                    my @keys = $self->_get_attributes('sshPublicKey');
1382
1383                    my @filters = $self->_get_attributes('sshPublicKeyFilter');
1384                    my $delUnknownSshKey = $self->_get_attributes('delUnknownSshKey');
1385
1386                    my %users = ( $self->id => 1 );
1387
1388                    if (@filters) {
1389                        foreach my $user ($self->base->search_objects('user', @filters, 'oalias=NULL')) {
1390                            my $ouser = $self->base->get_object('user', $user) or next;
1391                            $users{ $user } and next;
1392                            $users{ $user } = 1;
1393                            push(@keys, $ouser->_get_attributes('sshPublicKey'));
1394                        }
1395                    }
1396
1397                    return @keys
1398                        ? \@keys
1399                        : (@filters || $delUnknownSshKey
1400                            ? [ '# No key (' . DateTime->now->iso8601 . ')' ]
1401                            : undef);
1402                },
1403            },
1404            authorizedKeys => {
1405                multiple => 1,
1406                ro => 1,
1407            },
1408            currentEmployment => {
1409                managed => 1,
1410                ro => 1,
1411                reference => 'employment',
1412                get => sub {
1413                    my ($attr) = @_;
1414                    my $self = $attr->object;
1415
1416                    my $now = DateTime->now()->iso8601 . 'Z';
1417
1418                    my $sth = $self->base->db->prepare_cached(
1419                        q{
1420                        select name from employment where firstday <= ?::timestamp and
1421                        (lastday is null or lastday >= ?::timestamp - '1 days'::interval) and "user" = ?
1422                        limit 1
1423                        }
1424                    );
1425                    $sth->execute($now, $now, $self->id);
1426                    my $res = $sth->fetchrow_hashref;
1427                    $sth->finish;
1428                    if ($res) {
1429                        return $res->{name}
1430                    } else {
1431                        return;
1432                    }
1433                },
1434            },
1435            nextEmployment => {
1436                managed => 1,
1437                ro => 1,
1438                reference => 'employment',
1439                get => sub {
1440                    my ($attr) = @_;
1441                    my $self = $attr->object;
1442
1443                    my $now = DateTime->now()->iso8601 . 'Z';
1444
1445                    my $sth = $self->base->db->prepare_cached(
1446                        q{
1447                        select name from employment where firstday > ?::timestamp and "user" = ?
1448                        order by firstday asc
1449                        limit 1
1450                        }
1451                    );
1452                    $sth->execute($now, $self->id);
1453                    my $res = $sth->fetchrow_hashref;
1454                    $sth->finish;
1455                    if ($res) {
1456                        return $res->{name}
1457                    } else {
1458                        return;
1459                    }
1460                }
1461            },
1462            activeEmployment => {
1463                managed => 1,
1464                ro => 1,
1465                reference => 'employment',
1466                multiple => 1,
1467                get => sub {
1468                    my ($attr) = @_;
1469                    my $self = $attr->object;
1470
1471                    my $now = DateTime->now()->iso8601 . 'Z';
1472
1473                    my $sth = $self->base->db->prepare_cached(
1474                        q{
1475                        select name from employment where (lastday > ?::timestamp or lastday IS NULL) and "user" = ?
1476                        order by firstday asc
1477                        limit 1
1478                        }
1479                    );
1480                    $sth->execute($now, $self->id);
1481                    my @Res;
1482                    while (my  $res = $sth->fetchrow_hashref) {
1483                        push(@Res, $res->{name});
1484                    }
1485
1486                    return \@Res;
1487                }
1488            },
1489            prevEmployment => {
1490                managed => 1,
1491                ro => 1,
1492                reference => 'employment',
1493                get => sub {
1494                    my ($attr) = @_;
1495                    my $self = $attr->object;
1496
1497                    my $now = DateTime->now()->iso8601 . 'Z';
1498
1499                    my $sth = $self->base->db->prepare_cached(
1500                        q{
1501                        select name from employment where
1502                        (lastday is not null and lastday <= ?::timestamp - '1 days'::interval) and "user" = ?
1503                        order by firstday desc
1504                        limit 1
1505                        }
1506                    );
1507                    $sth->execute($now, $self->id);
1508                    my $res = $sth->fetchrow_hashref;
1509                    $sth->finish;
1510                    if ($res) {
1511                        return $res->{name}
1512                    } else {
1513                        return;
1514                    }
1515                }
1516            },
1517            appliedEmployement => {
1518                hide => 1,
1519                reference => 'employment',
1520            },
1521            contratTypeHistory => {
1522                reference => 'group',
1523                can_values => sub {
1524                    $base->search_objects('group', 'sutype=contrattype')
1525                },
1526                multiple => 1,
1527            },
1528            employmentHistory => {
1529                reference => 'employment',
1530                multiple => 1,
1531                ro => 1,
1532            },
1533            hosted => {
1534                formtype => 'CHECKBOX',
1535                label => l('Hosted'),
1536            },
1537            assigned => {
1538                formtype => 'CHECKBOX',
1539                label => l('Assigned'),
1540            },
1541            createRequestId => {
1542                label => l('Account Request id')
1543            },
1544            requestId => {
1545                label => l('Request id'),
1546            },
1547            nationality => {
1548                label => l('Nationality'),
1549            },
1550            nativeCountry => {
1551                label => l('Native country'),
1552            },
1553            firstAidTrainingValidity => {
1554                formtype => 'DATE',
1555                label => l('Validité formation SST'),
1556            },
1557    };
1558
1559    my $employmentro = sub {
1560        my $setting = $base->config('employment_lock_user') || 'any';
1561
1562        for ($setting) {
1563            /^always$/ and return 1;
1564            /^never$/ and return 0;
1565
1566            $_[0] or return 0;
1567
1568            /^any$/i and return $_[0]->listEmployment ? 1 : 0;
1569            /^active/i and do {
1570                return $_[0]->_get_c_field('currentEmployment')
1571                ? 1
1572                : $_[0]->_get_c_field('nextEmployment') ? 1 : 0;
1573            };
1574            /(\S+)=(\S+)/ and do {
1575                my $attr = $_[0]->_get_c_field($1);
1576                if (defined($attr)) {
1577                    if ($2 eq '*') {
1578                        return 1;
1579                    } elsif($2 eq $attr) {
1580                        return 1;
1581                    } else {
1582                        return 0;
1583                    }
1584                } else {
1585                    return 0;
1586                }
1587            };
1588        }
1589        return $_[0]->listEmployment ? 1 : 0; # default is any!
1590    };
1591
1592    foreach (_reported_atributes(), qw(department managerContact contratTypeHistory)) {
1593        $attrs->{$_}{ro} = $employmentro;
1594    }
1595
1596    $attrs->{expire}{ro} = sub {
1597        my $expireOn = $base->config('expireOn') || '';
1598        if ($expireOn eq 'never') {
1599            return 0;
1600        }
1601
1602        $employmentro->($_[0]);
1603    };
1604
1605    $class->SUPER::_get_attr_schema($base, $attrs)
1606}
1607
1608sub CreateAlias {
1609    my ($class, $base, $name, $for) = @_;
1610
1611    my $stAddAlias = $base->db->prepare_cached(
1612        q{INSERT INTO "user" (name, uidnumber,            gidnumber, oalias, oaliascache) values
1613                             (?,    -nextval('ikey_seq'), ?,         ?,      ?)}
1614    );
1615
1616    my $ref = $base->_derefObject($class->type, $for);
1617    my $res = $stAddAlias->execute($name, -1, $for, $ref ? $ref->id : undef);
1618    return $res ? 1 : 0;
1619}
1620
1621sub _get_state {
1622    my ($self, $state) = @_;
1623    for ($state) {
1624        /^expired$/ and do {
1625            my $attribute = $self->attribute('expire');
1626            $attribute->check_acl('r') or return;
1627            my $sth = $self->db->prepare_cached(
1628                q{ select coalesce(expire < now(), false) as exp from "user"
1629                where "user".name = ?}
1630            );
1631            $sth->execute($self->id);
1632            my $res = $sth->fetchrow_hashref;
1633            $sth->finish;
1634            return $res->{exp} ? 1 : 0;
1635        };
1636    }
1637}
1638
1639sub set_fields {
1640    my ($self, %data) = @_;
1641
1642    my $old;
1643    if (exists($data{department})) {
1644        $old = $self->_get_attributes('department');
1645    }
1646
1647    if (($data{department} || '') eq ($old || '')) {
1648        # We do not remove the group, there is no change
1649        $old = undef;
1650    }
1651
1652    if ($old) {
1653        # If the department is no longer a department
1654        # we do nothing
1655        my @names = $self->base->search_objects('group', 'name=' . $old, 'sutype=dpmt');
1656        if (! @names) {
1657            $old = undef;
1658        }
1659    }
1660
1661    my $res = $self->SUPER::set_fields(%data) or return;
1662
1663    if ($self->base->config('remove_old_dpmt') && $old) {
1664        $self->base->log(LA_DEBUG,
1665            "Removing %s from group %s (department change to %s)",
1666            $self->id,
1667            $old,
1668            $data{department} || '');
1669        $self->_delAttributeValue('memberOf', $old) or return;
1670        $res++;
1671    }
1672
1673    $res
1674}
1675
1676=head2 listEmployment
1677
1678Return the ordered list of contract
1679
1680=cut
1681
1682sub listEmployment {
1683    my ($self) = @_;
1684
1685    my $sth = $self->base->db->prepare_cached(
1686        q{
1687        select name from employment where "user" = ?
1688        order by lastday desc NULLS first
1689        }
1690    );
1691    $sth->execute($self->id);
1692    my @list = ();
1693    while (my $res = $sth->fetchrow_hashref) {
1694        push(@list, $res->{name});
1695    }
1696
1697    @list
1698}
1699
1700sub _reported_atributes { qw(contratType endcircuit hosted assigned requestId company employer) }
1701
1702=head2 applyCurrentEmployment
1703
1704Search the current employment is any and apply paramter to user
1705
1706=cut
1707
1708sub applyCurrentEmployment {
1709    my ($self) = @_;
1710
1711    # Get current employment name
1712    my $currentempl = $self->get_attributes('currentEmployment') || '';
1713
1714    $self->base->log(
1715        LA_DEBUG,
1716        "Applying Employement %s to user %s",
1717        $currentempl || '(none)',
1718        $self->id
1719    );
1720
1721    if (my $currentemployment = $self->base->get_object('employment', $currentempl)) {
1722
1723        # If an employement apply we set the value to the user object
1724
1725        $self->computeEmploymentDate;
1726
1727        my %attrsets = (
1728            appliedEmployement => $currentemployment->id,
1729        );
1730        foreach my $attr (_reported_atributes(), qw(department managerContact)) {
1731            my $uval = $self->get_attributes($attr) || '';
1732            my $cval = $currentemployment->get_attributes($attr) || '';
1733
1734            if ($attr eq 'managerContact') {
1735                if (!$cval) {
1736                    my $dpmt  = $currentemployment->get_attributes('department') or last;
1737                    my $odmpt = $currentemployment->base->get_object('group', $dpmt) or last;
1738                    $cval = $odmpt->get_attributes('managedBy');
1739                }
1740            }
1741
1742            if ($uval ne $cval) {
1743                my $oattr = $currentemployment->base->attribute('user', $attr);
1744                $attrsets{$oattr->iname} = $cval;
1745            }
1746        }
1747
1748        if (keys %attrsets) {
1749            if (my $res = $self->set_fields(%attrsets)) {
1750                $self->ReportChange('Update', 'Attr %s updated to match Employment %s', join(', ', sort keys %attrsets), $currentemployment->id);
1751                return $res;
1752            }
1753        } else {
1754            return 1;
1755        }
1756    } else {
1757        # No current employment, resetting values:
1758
1759        return $self->_resetEmployment;
1760    }
1761
1762}
1763
1764# Reset attribute value set by employment
1765# except managerContact and expire
1766
1767sub _resetEmployment {
1768    my ($self) = @_;
1769
1770    $self->computeEmploymentDate;
1771
1772    my %changes = (
1773        appliedEmployement => undef,
1774    );
1775
1776    my @attributesToReset = (_reported_atributes(), qw(department));
1777
1778    foreach my $attr (@attributesToReset) {
1779        my $default = $self->base->config("unemployment.$attr") || '';
1780        my $old = $self->_get_attributes($attr) || '';
1781
1782        if ($old ne $default) {
1783            $changes{$attr} = $default || undef;
1784        }
1785    }
1786
1787    if(!$self->get_attributes('managerContact')) {
1788        if (my $next = $self->_get_attributes('nextEmployment')) {
1789            my $onext = $self->base->get_object('employment', $next);
1790            $changes{'manager'} = $onext->_get_attributes('managerContact');
1791        }
1792    }
1793
1794    # Managing expire:
1795    # If we found next status apply, keep it for expiration
1796    # Otherwise we reset to previous end of status
1797
1798    if (my $expire = $self->_computeEndEmployment($self->base->config('employment_delay') || 0)) {
1799        $changes{ 'expire' } = $expire;
1800    } elsif (my $prevEmployment = $self->_get_attributes('prevEmployment')) {
1801        my $oprev = $self->base->get_object('employment', $prevEmployment);
1802        $changes{ 'expire' } = $oprev->_get_attributes('lastday');
1803    } elsif (($self->base->config('unemployed_expire') ||'') ne 'no') {
1804        if (my $def = $self->base->{defattr}{'user.expire'}) {
1805            $changes{ 'expire' } = $def;
1806        }
1807    }
1808
1809
1810    if (%changes) {
1811        if ($self->set_fields(%changes)) {
1812            $self->base->log(LA_NOTICE, "Updating user %s to match unemployment", $self->id);
1813            $self->ReportChange('Update', 'Update %s to match unemployment', join(', ', sort keys %changes));
1814            return 1;
1815        } else {
1816            return 0;
1817        }
1818    } else {
1819        return 1;
1820    }
1821
1822}
1823
1824=head2 computeEmploymentDate
1825
1826Compute and copy to user start and end employment date
1827
1828=cut
1829
1830sub computeEmploymentDate {
1831    my ($self) = @_;
1832
1833    my $currentemployment = $self->get_attributes('currentEmployment') || '';
1834
1835    my $expire = str2time($self->_get_attributes('expire') || '1970-01-01T00:00:00');
1836
1837    my %changes;
1838    my @employmentDate = qw(
1839        endEmployment   endStrictEmployment   endCurrentEmployment   endLastEmployment
1840        startEmployment startStrictEmployment startCurrentEmployment startFirstEmployment
1841        arrival departure
1842    );
1843    foreach (@employmentDate) {
1844        my $old = $self->_get_attributes($_) || '';
1845        my $new = $self->_get_attributes("_$_") || '';
1846        if ($old ne $new) {
1847            $changes{$_} = $new || undef;
1848        }
1849    }
1850
1851    # If there is no current employment we try to find any to not let expire
1852    # unset
1853
1854    my $expireOn = $self->base->config('expireOn') || '';
1855    if (!grep { $_ eq $expireOn } (@employmentDate, '', 'never')) {
1856        $self->base->log(LA_ERR, "expireOn set to invalid parameter %s, using endEmployment instead", $expireOn);
1857        $expireOn = undef;
1858    }
1859    $expireOn ||= 'endEmployment';
1860
1861    # We check if matching start exists to know if using end* for expiration is
1862    # safe, even undef
1863    my %end2start = (
1864        endEmployment        => 'startEmployment',
1865        endStrictEmployment  => 'startStrictEmployment',
1866        endCurrentEmployment => 'startCurrentEmployment',
1867        endLastEmployment    => 'startFirstEmployment',
1868    );
1869
1870    # TODO rework this, working code but bloat
1871    if ($expireOn ne 'never') {
1872        my $endemploy = '';
1873        if ($self->_get_attributes("_$end2start{$expireOn}")) {
1874            $endemploy = $self->_get_attributes("_$expireOn") || '';
1875            $endemploy ||= 'UNCHANGED' unless($currentemployment);
1876        } elsif (($self->base->config('unemployed_expire') ||'') eq 'no') {
1877            $endemploy = '';
1878        } else {
1879            # No expiration date apply, don't touch
1880            $endemploy = 'UNCHANGED';
1881        }
1882
1883        if ($endemploy ne 'UNCHANGED') {
1884            my $nextexpire = str2time( $endemploy || '1970-01-01T00:00:00' );
1885
1886            if ($expire != $nextexpire) {
1887                $changes{expire} = $endemploy;
1888            }
1889        }
1890    }
1891
1892    if (keys %changes) {
1893        $self->base->log(LA_DEBUG, 'Applying employment state to user %s for field %s', $self->id, join(', ', keys %changes));
1894        $self->ReportChange('Update', 'Update %s to match employment', join(', ', sort keys %changes));
1895        if (exists($changes{expire})) {
1896            $self->ReportChange('Update', 'Expire update to %s to match employment', ($changes{expire} || '(none)'));
1897            $self->base->log(LA_DEBUG, 'New expiration is %s for user %s', ($changes{expire} || '(none)'), $self->id);
1898        }
1899        $self->set_fields(%changes);
1900    } else {
1901        $self->base->log(LA_DEBUG, 'No employment change for user %s', $self->id);
1902    }
1903
1904    $self->_computeEmploymentHistory();
1905
1906    return 1;
1907}
1908
1909sub _computeStartEmployment {
1910    my ($self, $delay, $any, $workday) = @_;
1911
1912    $delay ||= 0;
1913    my $start;
1914    my $nstart;
1915
1916    if (my $next = $self->_get_attributes('nextEmployment')) {
1917        my $onext = $self->base->get_object('employment', $next);
1918        $nstart = DateTime->from_epoch(epoch => str2time($onext->_get_attributes('firstday')));
1919        $nstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1920    }
1921
1922    my $list_empl = $self->base->db->prepare_cached(q{
1923        SELECT *, (lastday is null or lastday >= now() - '1days'::interval) as "current" FROM employment
1924        WHERE "user" = ? and firstday < now()
1925        order by firstday desc
1926        });
1927    $list_empl->execute($self->id);
1928
1929    while (my $res = $list_empl->fetchrow_hashref) {
1930        if ($res->{current}) {
1931        } elsif ($nstart) {
1932            my $prevend = DateTime->from_epoch(epoch => str2time($res->{lastday}));
1933            $prevend->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1934            my $tstart = $nstart->clone;
1935            $tstart->subtract(days => $delay + 1);
1936            if ($tstart->ymd gt $prevend->ymd) {
1937                last;
1938            }
1939        } elsif ((!$res->{current}) && (!$any)) {
1940            last;
1941        }
1942        $nstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
1943        $nstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1944        $start = $nstart->clone;
1945    }
1946    $list_empl->finish;
1947
1948    $start ||= $nstart if ($any);
1949
1950    if ($start) {
1951        if ($workday) {
1952            my $day_of_week = $start->day_of_week;
1953            $start->add(days =>
1954                $day_of_week == 6 ? 2 :
1955                $day_of_week == 7 ? 1 : 0
1956            );
1957        }
1958    }
1959
1960    return $start ? $start->iso8601 : undef
1961}
1962
1963sub _computeEndEmployment {
1964    my ($self, $delay, $any, $workday) = @_;
1965
1966    $delay ||= 0;
1967    my $end;
1968    my $pend;
1969
1970    if (my $prev = $self->_get_attributes('prevEmployment')) {
1971        my $oprev = $self->base->get_object('employment', $prev);
1972        $pend = DateTime->from_epoch(epoch => str2time($oprev->_get_attributes('lastday')));
1973        $pend->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1974        $pend->add(hours => 23, minutes => 59, seconds => 59);
1975    }
1976
1977    my $list_empl = $self->base->db->prepare_cached(q{
1978        SELECT *, firstday <= now() as "current" FROM employment WHERE "user" = ? and
1979        (lastday is null or lastday >= now() - '1 days'::interval)
1980        order by firstday asc
1981        });
1982    $list_empl->execute($self->id);
1983    while (my $res = $list_empl->fetchrow_hashref) {
1984        if (!$res->{lastday}) {
1985            # Ultimate employment.
1986            $list_empl->finish;
1987            return undef;
1988        }
1989        if ($res->{current}) {
1990        } elsif ($end) {
1991            my $nextstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
1992            $nextstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1993            my $tend = $end->clone;
1994            $tend->add(days => $delay + 1);
1995            if ($tend->ymd lt $nextstart->ymd) {
1996                last;
1997            }
1998        } elsif ($pend) {
1999            my $nextstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
2000            $nextstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
2001            my $tend = $pend->clone;
2002            $tend->add(days => $delay + 1);
2003            if ($tend->ymd lt $nextstart->ymd) {
2004                last;
2005            }
2006            $end = $tend
2007        } elsif ((!$res->{current}) && (!$any)) {
2008            last;
2009        }
2010        $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
2011        $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
2012        $end->add(hours => 23, minutes => 59, seconds => 59);
2013    }
2014    $list_empl->finish;
2015
2016    $end ||= $pend if($any);
2017
2018    if ($end) {
2019        if ($workday) {
2020            my $day_of_week = $end->day_of_week;
2021            $end->subtract(days =>
2022                $day_of_week == 7 ? 2 :
2023                $day_of_week == 6 ? 1 : 0
2024            );
2025        }
2026    }
2027
2028    return $end ? $end->iso8601 : undef
2029}
2030
2031# Compute a summary of employement and store the value
2032# into an attribute
2033
2034sub _computeEmploymentHistory {
2035    my ($self) = @_;
2036
2037    my %changes;
2038
2039    {
2040        my $sth = $self->db->prepare_cached(q{
2041            select employment.name from employment
2042            where "user" = ? and employment.firstday < now()
2043        });
2044        $sth->execute($self->id);
2045        my @values;
2046        while (my $res = $sth->fetchrow_hashref) {
2047            push(@values, $res->{name});
2048        }
2049        $changes{"employmentHistory"} = \@values;
2050    }
2051
2052    foreach my $attribute (qw(contratType)) {
2053        my $sth = $self->db->prepare_cached(q{
2054            select employment_attributes.value from employment
2055            join employment_attributes
2056            on employment.ikey = employment_attributes.okey
2057            where "user" = ? and employment_attributes.attr = ?
2058            and employment.firstday < now()
2059            group by employment_attributes.value
2060            });
2061        $sth->execute($self->id, $attribute);
2062        my @values;
2063        while (my $res = $sth->fetchrow_hashref) {
2064            push(@values, $res->{value});
2065        }
2066        $changes{$attribute . "History"} = \@values;
2067    }
2068
2069    $self->set_fields(%changes);
2070}
2071
2072=head2 storeBannedPassword($epassword)
2073
2074Add an encrypted password to untrust list
2075
2076=cut
2077
2078sub storeBannedPassword {
2079    my ($self, $EncPass) = @_;
2080
2081    my @banned = sort { $b cmp $a } $self->_get_attributes('bannedPassword');
2082    my $now = DateTime->now; 
2083    unshift(@banned, $now->iso8601 . ';' . $EncPass);
2084    $self->set_fields('bannedPassword', [ grep { $_ } @banned ]);
2085
2086}
2087
2088=head2 banCurrentPassword
2089
2090Store the current password as banned
2091
2092=cut
2093
2094sub banCurrentPassword {
2095    my ($self) = @_;
2096   
2097    my $old = $self->get_field('userPassword') or return;
2098    $self->storeBannedPassword($old);
2099}
2100
2101sub check_password {
2102    my ( $self, $password ) = @_;
2103
2104    my $res = $self->SUPER::check_password($password);
2105    if ($res !~ /^ok$/) {
2106        return $res;
2107    }
2108
2109    foreach my $banned ($self->_get_attributes('bannedPassword')) {
2110        my ($date, $oldPassword) = $banned =~ /^([^;]*);(.*)/;
2111        if (crypt($password, $oldPassword) eq $oldPassword) {
2112            return "Banned password, cannot be used anymore";
2113        }
2114    }
2115
2116    return 'ok';
2117}
2118
2119sub _set_password {
2120    my ($self, $clear_pass) = @_;
2121    if (my $attr = $self->base->attribute($self->type, 'userPassword')) {
2122        my $field = $attr->iname;
2123
2124        # Storing as old password
2125        my @olds = sort { $b cmp $a } $self->_get_attributes('oldPassword');
2126        if (my $old = $self->get_field('userPassword')) {
2127            my $now = DateTime->now; 
2128            unshift(@olds, $now->iso8601 . ';' . $old);
2129            $self->set_fields('oldPassword', [ grep { $_ } @olds[0 .. 14] ]);
2130        }
2131
2132        my $res = $self->set_fields($field, $self->base->passCrypt($clear_pass));
2133        if ($res) {
2134            if ($self->base->get_global_value('rsa_public_key')) {
2135                $self->setCryptPassword($clear_pass) or return;
2136            }
2137        }
2138
2139        $self->set_fields('passwordLastSet', DateTime->now->datetime);
2140        $self->base->log(LA_NOTICE,
2141            'Mot de passe changé pour %s',
2142            $self->id
2143        );
2144
2145
2146        return $res;
2147    } else {
2148        $self->log(LA_WARN,
2149            "Cannot set password: userPassword attributes is unsupported");
2150    }
2151}
2152
2153=head2 setCryptPassword($clear_pass)
2154
2155Store password encrypted using RSA encryption.
2156
2157=cut
2158
2159sub setCryptPassword {
2160    my ($self, $clear_pass) = @_;
2161    if (my $serialize = $self->base->get_global_value('rsa_public_key')) {
2162        my $public = Crypt::RSA::Key::Public->new;
2163        $public = $public->deserialize(String => [ $serialize ]);
2164        my $rsa = new Crypt::RSA ES => 'PKCS1v15';
2165        my $rsa_password = $rsa->encrypt (
2166            Message    => $clear_pass,
2167            Key        => $public,
2168            Armour     => 1,
2169        ) || die $rsa->errstr();
2170        if (!$self->_set_c_fields('encryptedPassword', $rsa_password)) {
2171            $self->log(LA_ERR,
2172                "Cannot set 'encryptedPassword' attribute for object %s/%s",
2173                $self->type, $self->id,
2174            );
2175            return;
2176        }
2177    }
2178    $self->ReportChange('Password', 'Password stored using internal key');
2179    return 1;
2180}
2181
2182=head2 _InjectCryptPasswd($cryptpasswd)
2183
2184Inject a password encrypted using standard UNIX method.
2185
2186The passwrod will be used to authenticate user inside the application but it
2187will not be transmit to any other database.
2188
2189=cut
2190
2191sub _InjectCryptPasswd {
2192    my ($self, $cryptpasswd) = @_;
2193
2194    if (my $current = $self->get_field('userPassword')) {
2195        if ($cryptpasswd eq $current) {
2196            return 1;
2197        }
2198    }
2199    my $res = $self->set_fields('userPassword', $cryptpasswd);
2200
2201    if ($res) {
2202        $self->base->log(LA_NOTICE, 'Crypted password injected for %s', $self->id);
2203        return 1;
2204    } else {
2205        $self->base->log(LA_ERR, 'Cannot inject crypted password for %s', $self->id);
2206        return 0;
2207    }
2208}
2209
2210=head2 GenPasswordResetId
2211
2212Return a new id allowing passowrd reset
2213
2214=cut
2215
2216sub GenPasswordResetId {
2217    my ($self) = @_;
2218
2219    my $id = LATMOS::Accounts::Utils::genpassword(length => 32);
2220
2221    my $sth = $self->base->db->prepare_cached(q{
2222        INSERT INTO passwordreset (id, "user") values (?,?)
2223    });
2224
2225    if ($sth->execute($id, $self->id)) {
2226        return $id;
2227    } else {
2228        return;
2229    }
2230}
2231
2232=head2 SendPasswordReset($url)
2233
2234Generate a password reset Id and the to the user.
2235
2236C<$url> is the URL where the password can changed (printf forward, the %s is
2237replaced by the request id)
2238
2239=cut
2240
2241sub SendPasswordReset {
2242    my ($self, $url) = @_;
2243
2244    my $id = $self->GenPasswordResetId;
2245
2246    my $mail = $self->_get_attributes('mail') or do {
2247        $self->base->log(LA_ERR, "Cannot sent reset password mail: no mail found");
2248        return;
2249    };
2250
2251    my $MailSubject = $self->base->la->val('_default_', 'mailSubject', 'LATMOS::Accounts');
2252
2253    my %mail = (
2254        Subject => "$MailSubject: pasword reset",
2255        'X-LATMOS-Reason' => 'Password Reset',
2256        to => $mail,
2257    );
2258
2259    if (my $otherEmail = $self->_get_attributes('otherEmail')) {
2260        $mail{cc} = $otherEmail;
2261    }
2262
2263    my $vars = {
2264        url => sprintf($url, $id),
2265    };
2266    $vars->{id} =  $id;
2267    $vars->{obj} = $self;
2268
2269    my $lamail = LATMOS::Accounts::Mail->new(
2270        $self->base->la,
2271        'passwordreset.mail',
2272    );
2273
2274    if ($lamail->process(\%mail, $vars)) {
2275        $self->base->log(LA_NOTICE,
2276            "Reset password sent to %s for user %s",
2277            $mail{to},
2278            $self->id,
2279        );
2280        return 1;
2281    } else {
2282        return;
2283    }
2284
2285}
2286
2287=head2 CheckPasswordResetId($id)
2288
2289Return True if the reset password ID can be found and is less than one day old
2290
2291=cut
2292
2293sub CheckPasswordResetId {
2294    my ($self, $id) = @_;
2295
2296    my $sth = $self->base->db->prepare_cached(q{
2297        SELECT * FROM passwordreset WHERE
2298            "user" = ? and
2299            id = ? and
2300            "create" >= now() - '1 days'::interval
2301    });
2302    $sth->execute($self->id, $id);
2303
2304    my $res = $sth->fetchrow_hashref;
2305    $sth->finish;
2306
2307    return $res ? 1 : 0;
2308}
2309
2310=head2 DeletePasswordId($id)
2311
2312Delete password reset C<$id> and all expired request
2313
2314=cut
2315
2316sub DeletePasswordId {
2317    my ($self, $id) = @_;
2318
2319    my $sth = $self->base->db->prepare_cached(q{
2320        DELETE FROM passwordreset WHERE
2321        "user" = ? AND (id = ? or "create" < now() - '1 days'::interval)
2322    });
2323
2324    $sth->execute($self->id, $id);
2325}
2326
23271;
2328
2329__END__
2330
2331=head1 SEE ALSO
2332
2333=head1 AUTHOR
2334
2335Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
2336
2337=head1 COPYRIGHT AND LICENSE
2338
2339Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
2340
2341This library is free software; you can redistribute it and/or modify
2342it under the same terms as Perl itself, either Perl version 5.10.0 or,
2343at your option, any later version of Perl 5 you may have available.
2344
2345=cut
Note: See TracBrowser for help on using the repository browser.