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

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

Fix address change propagation

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