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

Last change on this file since 1904 was 1898, checked in by nanardon, 7 years ago

Avoid undef

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