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

Last change on this file since 2474 was 2474, checked in by nanardon, 3 years ago

Add shadowLastChange computation

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