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

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

Return non empty authorizedKeys if delunknown is enable

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