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

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

Cleanup useless code

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