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

Last change on this file since 1904 was 1865, checked in by nanardon, 8 years ago

Merge branch

  • Property svn:keywords set to Id Rev
File size: 19.5 KB
Line 
1package LATMOS::Accounts::Bases::Objects;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use overload '""' => 'stringify';
8
9use LATMOS::Accounts::Log;
10use LATMOS::Accounts::Bases::Attributes;
11use Crypt::Cracklib;
12
13our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
14
15=head1 NAME
16
17LATMOS::Accounts::Bases::Objects - Base class for account objects
18
19=head1 SYNOPSIS
20
21  use LATMOS::Accounts::Bases::Objects;
22  LATMOS::Accounts::Bases::Objects->new($base, $type, $id);
23
24=head1 DESCRIPTION
25
26=head1 FUNCTIONS
27
28=cut
29
30=head2 is_supported
31
32If exists, must return true or false if the object is supported or not
33
34=cut
35
36=head2 list($base)
37
38List object supported by this module existing in base $base
39
40Must be provide by object class
41
42    sub list {
43        my ($class, $base) = @_;
44    }
45
46=cut
47
48=head2 listReal($base)
49
50List object supported by this module existing in base $base
51
52Can be override by base driver. The result must exclude specials object such alias.
53
54=cut
55
56sub listReal {
57    my ($class, $base) = @_;
58    $class->list($base);
59}
60
61=head2 list_from_rev($base, $rev)
62
63List objects create or modified after base revision C<$rev>.
64
65=cut
66
67=head2 new($base, $id)
68
69Create a new object having $id as uid.
70
71=cut
72
73sub new {
74    my ($class, $base, $id, @args) = @_;
75    # So can be call as $class->SUPER::new()
76    bless {
77        _base => $base,
78        _type => lc(($class =~ m/::([^:]*)$/)[0]),
79        _id => $id,
80    }, $class;
81}
82
83=head2 _create($class, $base, $id, %data)
84
85Must create a new object in database.
86
87Is called if underling base does not override create_object
88
89    sub _create(
90        my ($class, $base, $id, %data)
91    }
92
93=cut
94
95=head2 type
96
97Return the type of the object
98
99=cut
100
101sub type {
102    my ($self) = @_;
103    if (ref $self) {
104        return $self->{_type}
105    } else {
106        return lc(($self =~ /::([^:]+)$/)[0]);
107    }
108}
109
110=head2 base
111
112Return the base handle for this object.
113
114=cut
115
116sub base {
117    return $_[0]->{_base}
118}
119
120=head2 id
121
122Must return the unique identifier for this object
123
124=cut
125
126sub id {
127    my ($self) = @_;
128    $self->{_id}
129}
130
131=head2 Iid
132
133Return internal id if different from Id
134
135=cut
136
137sub Iid {
138    my ($self) = @_;
139    $self->id
140}
141
142=head2 stringify
143
144Display object as a string
145
146=cut
147
148sub stringify {
149    my ($self) = @_;
150
151    return $self->id
152}
153
154=head2 list_canonical_fields($for)
155
156Object shortcut to get the list of field supported by the object.
157
158=cut
159
160sub list_canonical_fields {
161    my ($self, $for) = @_;
162    $for ||= 'rw';
163    $self->_canonical_fields($for);
164}
165
166=head2 attribute ($attribute)
167
168Return L<LATMOS::Accounts::Bases::Attributes> object for C<$attribute>
169
170=cut
171
172sub attribute {
173    my ($self, $attribute) = @_;
174
175    my $attrinfo;
176    if (! ref $attribute) {
177        $attrinfo = $self->_get_attr_schema(
178            $self->base)->{$attribute}
179        or return;
180        $attrinfo->{name} = $attribute;
181    } else {
182        $attrinfo = $attribute;
183    }
184
185    return LATMOS::Accounts::Bases::Attributes->new(
186        $attrinfo,
187        $self,
188    );
189}   
190
191sub _canonical_fields {
192    my ($class, $base, $for) = @_;
193    $for ||= 'rw';
194    my $info = $base->_get_attr_schema($class->type);
195    my @attrs = map { $base->attribute($class->type, $_) } keys %{$info || {}};
196    @attrs = grep { ! $_->ro } @attrs if($for =~ /w/);
197    @attrs = grep { $_->readable } @attrs if($for =~ /r/);
198    map { $_->name } grep { !$_->hidden }  @attrs;
199}
200
201=head2 get_field($field)
202
203Return the value for $field, must be provide by data base.
204
205    sub get_field {
206        my ($self, $field)
207    }
208
209=cut
210
211=head2 get_c_field($cfield)
212
213Return the value for canonical field $cfield.
214
215Call driver specific get_field()
216
217=cut
218
219sub get_c_field {
220    my ($self, $cfield) = @_;
221    $self->base->check_acl($self, $cfield, 'r') or do {
222        $self->base->log(LA_ERR, "Permission denied to get %s/%s",
223            $self->id, $cfield
224        );
225        return;
226    };
227    return $self->_get_c_field($cfield);
228}
229
230=head2 get_attributes($attr)
231
232Like get_c_field but always return an array
233
234=cut
235
236sub get_attributes {
237    my ($self, $cfield) = @_;
238    my $res = $self->get_c_field($cfield);
239    if ($res) {
240        return(ref $res ? @{$res} : $res);
241    } else {
242        return;
243    }
244}
245
246sub _get_attributes {
247    my ($self, $cfield) = @_;
248    my $res = $self->_get_c_field($cfield);
249    if ($res) {
250        return(ref $res ? @{$res} : ($res));
251    } else {
252        return;
253    }
254}
255
256sub _get_c_field {
257    my ($self, $cfield) = @_;
258    my $attribute = $self->attribute($cfield) or do {
259        $self->base->log(LA_WARN, "Unknow attribute $cfield");
260        return;
261    };
262    $attribute->readable or do {
263        $self->base->log(LA_WARN, "Attribute $cfield is not readable");
264        return;
265    };
266    return $attribute->get; 
267}
268
269=head2 GetAttributeValue($cfield)
270
271Return the value to exposed to other base
272
273=cut
274
275sub GetAttributeValue {
276    my ($self, $cfield) = @_;
277
278    return $self->get_c_field($cfield);
279}
280
281
282=head2 queryformat ($fmt)
283
284Return formated string according C<$fmt>
285
286=cut
287
288sub queryformat {
289    my ($self, $fmt) = @_;
290    $fmt =~ s/\\n/\n/g;
291    $fmt =~ s/\\t/\t/g;
292    $fmt =~ s!
293        (?:%\{([^:}]*)(?::([^}]+))?\})
294        !
295        my $val = $self->get_c_field($1);
296        sprintf('%' . ($2 || 's'), ref $val ? join(',', @$val) : (defined($val) ? $val : ''))
297        !egx;
298    $fmt;
299}
300
301=head2 set_fields(%data)
302
303Set values for this object. %data is a list or peer field => values.
304
305    sub set_fields {
306        my ($self, %data) = @_;
307    }
308
309=cut
310
311=head2 checkValues ($base, $obj, %attributes)
312
313Allow to pre-check values when object are modified or created
314
315C<$obj> is either the new id at object creation or the object itself on modification.
316
317=cut
318
319sub checkValues {
320    my ($class, $base, $obj, %attributes) = @_;
321
322    return 1;
323}
324
325=head2 check_allowed_values ($attr, $values)
326
327Check if value C<$values> is allowed for attributes C<$attr>
328
329=cut
330
331sub check_allowed_values {
332    my ($self, $attr, $values) = @_;
333    $self->base->check_allowed_values($self->type, $attr, $values);
334}
335
336=head2 attr_allow_values ($attr)
337
338Return allowed for attribute C<$attr>
339
340=cut
341
342sub attr_allow_values {
343    my ($self, $attr) = @_;
344    return $self->base->obj_attr_allowed_values(
345        $self->type,
346        $attr,
347    );
348}
349
350=head2 set_c_fields(%data)
351
352Set values for this object. %data is a list or peer
353canonical field => values. Fields names are translated.
354
355=cut
356
357sub set_c_fields {
358    my ($self, %cdata) = @_;
359    foreach my $cfield (keys %cdata) {
360        $self->base->check_acl($self, $cfield, 'w') or do { 
361            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
362                $self->type, $self->id, "permission denied");
363            return;
364        };
365    }
366
367    foreach my $cfield (keys %cdata) {
368        $self->check_allowed_values($cfield, $cdata{$cfield}) or do {
369            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
370                $self->type, $self->id, "non authorized value");
371            return;
372        };
373    }
374
375    $self->_set_c_fields(%cdata);
376}
377
378sub _set_c_fields {
379    my ($self, %cdata) = @_;
380    my %data;
381    my $res = 0;
382    foreach my $cfield (keys %cdata) {
383        my $attribute = $self->attribute($cfield) or do {
384            $self->base->log(LA_ERR,
385                "Cannot set unsupported attribute %s to %s (%s)",
386                $cfield, $self->id, $self->type
387            );
388            return;
389        };
390        $attribute->ro and do {
391            $self->base->log(LA_ERR,
392                "Cannot set read-only attribute %s to %s (%s)",
393                $cfield, $self->id, $self->type
394            );
395            return;
396        };
397
398        if (!$attribute->checkinput($cdata{$cfield})) {
399            $self->base->log(LA_ERR,
400                "Value for attribute %s to %s (%s) does not match requirements",
401                $cfield, $self->id, $self->type
402            );
403            return;
404        };
405    }
406
407    if (!$self->checkValues($self->base, $self, %cdata)) {
408        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
409        $self->base->log(LA_ERR,
410            "Cannot update %s (%s): wrong value%s",
411            $self->id, $self->type,
412            ($last ? ": $last" : $last)
413        );
414        return;
415    }
416
417    my %updated = ();
418    foreach my $cfield (keys %cdata) {
419        my $attribute = $self->attribute($cfield) or do {
420            $self->base->log(LA_ERR,
421                "Cannot set unsupported attribute %s to %s (%s)",
422                $cfield, $self->id, $self->type
423            );
424            return;
425        };
426        if ($attribute->set($cdata{$cfield})) {
427            $updated{$cfield} = $attribute->monitored;
428        }
429    }
430   
431    if (keys %updated) {
432        $self->ReportChange('Update', 'Attributes %s have been updated', join(', ', sort keys %updated));
433        foreach (sort keys %updated) {
434            $self->ReportChange('Attributes', '%s set to %s', $_, 
435                (ref $cdata{$_}
436                    ? join(', ', sort @{ $cdata{$_} })
437                    : $cdata{$_}) || '(none)')
438                if ($updated{$_});
439        }
440    }
441    return scalar(keys %updated);
442}
443
444=head2 addAttributeValue($attribute, $value)
445
446Add a value to a multivalue attributes
447
448=cut
449
450sub _addAttributeValue {
451    my ($self, $attribute, @values) = @_;
452
453    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
454    $self->_set_c_fields($attribute => [ @oldvalues, @values ]);
455}
456
457sub addAttributeValue {
458    my ($self, $attribute, @values) = @_;
459
460    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
461    $self->set_c_fields($attribute => [ @oldvalues, @values ]);
462}
463
464=head2 delAttributeValue($attribute, $value)
465
466Remove a value to a multivalue attributes
467
468=cut
469
470sub _delAttributeValue {
471    my ($self, $attribute, @values) = @_;
472
473    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
474
475    foreach my $value (@values) {
476        @oldvalues = grep { $_ ne $value } @oldvalues;
477    }
478
479    $self->_set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
480}
481
482sub delAttributeValue {
483    my ($self, $attribute, @values) = @_;
484
485    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
486
487    foreach my $value (@values) {
488        @oldvalues = grep { $_ ne $value } @oldvalues;
489    }
490
491    $self->set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
492}
493
494=head2 set_password($password)
495
496Set the password into the database, $password is the clear version
497of the password.
498
499This function store it into userPassword canonical field if supported
500using crypt unix and md5 algorythm (crypt md5), the salt is 8 random
501caracters.
502
503The base driver should override it if another encryption is need.
504
505=cut
506
507sub set_password {
508    my ($self, $clear_pass) = @_;
509    if ($self->base->check_acl($self, 'userPassword', 'w')) {
510        if ($self->_set_password($clear_pass)) {
511             $self->ReportChange('Password', 'user password has changed');
512             return 1;
513        } else {
514            return;
515        }
516    } else {
517        $self->base->log(LA_ERROR, "Permission denied for %s to change its password",
518            $self->id);
519        return;
520    }
521}
522
523sub _set_password {
524    my ($self, $clear_pass) = @_;
525    if (my $attribute = $self->base->attribute($self->type, 'userPassword')) {
526        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
527        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
528        my $res = $self->set_fields($attribute->iname, crypt($clear_pass, '$1$' . $salt));
529        $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id)
530            if($res);
531        return $res;
532    } else {
533        $self->base->log(LA_WARN,
534            "Cannot set password: userPassword attributes is unsupported");
535    }
536}
537
538=head2 check_password ($password)
539
540Check given password is secure using L<Crypt::Cracklib>
541
542=cut
543
544sub check_password {
545    my ( $self, $password ) = @_;
546    my $dictionary;
547
548    if ($password !~ /^[[:ascii:]]*$/) {
549       return "the password must contains ascii characters only";
550    }
551
552    return fascist_check($password, $dictionary);
553}
554
555=head2 search ($base, @filter)
556
557Search object matching C<@filter>
558
559=cut
560
561sub search {
562    my ($class, $base, @filter) = @_;
563    my @results;
564    my %parsed_filter;
565    while (my $item = shift(@filter)) {
566        # attr=foo => no extra white space !
567        # \W is false, it is possible to have two char
568        my ($attr, $mode, $val) = $item =~ /^(\w+)(?:(\W)(.+))?$/ or next;
569        if (!$mode) {
570            $mode = '~';
571            $val = shift(@filter);
572        }
573        push(
574            @{$parsed_filter{$attr}},
575            {
576                attr => $attr,
577                mode => $mode,
578                val  => $val,
579            }
580        );
581    }
582    foreach my $id ($base->list_objects($class->type)) {
583        my $obj = $base->get_object($class->type, $id);
584        my $match = 1;
585        foreach my $field (keys %parsed_filter) {
586            $base->attribute($class->type, $field) or
587                la_log(LA_WARN, "Unsupported attribute %s", $field);
588            my $tmatch = 0;
589            foreach (@{$parsed_filter{$field}}) {
590                my $value = $_->{val};
591                my $fval = $obj->_get_c_field($field) || '';
592                if ($value eq '*') {
593                    if ($fval ne '') {
594                        $tmatch = 1;
595                        last;
596                    }
597                } elsif ($value eq '!') {
598                    if ($fval eq '') {
599                        $match = 1;
600                        last;
601                    }
602                } elsif ($_->{mode} eq '=') {
603                    if ($fval eq $value) {
604                        $tmatch = 1;
605                        last;
606                    }
607                } elsif($_->{mode} eq '~') {
608                    if ($fval =~ m/\Q$value\E/i) {
609                        $tmatch = 1;
610                        last;
611                    }
612                }
613            }
614            $match = 0 unless($tmatch);
615        }
616        push(@results, $id) if($match);
617    }
618    @results;
619}
620
621=head2 attributes_summary ($base, $attribute)
622
623Return list of values existing in base for C<$attribute>
624
625=cut
626
627sub attributes_summary {
628    my ($class, $base, $attribute) = @_;
629    my $attr = $base->attribute($class->type, $attribute) or do {
630        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
631        return;
632    };
633    if (!$attr->readable) {
634        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
635        return;
636    }
637    if (!$base->check_acl($class->type, $attribute, 'r')) {
638        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
639        return;
640    }
641    my %values;
642    foreach my $id ($base->list_objects($class->type)) {
643        my $obj = $base->get_object($class->type, $id);
644        my $value = $obj->_get_c_field($attribute);
645        if ($value) {
646            if (ref $value) {
647                foreach (@$value) {
648                    $values{$_} = 1;
649                }
650            } else {
651                $values{$value} = 1;
652            }
653        }
654    }
655    return sort(keys %values);
656}
657
658=head2 attributes_summary_by_object ($base, $attribute)
659
660Return list of peer object <=> values
661
662=cut
663
664sub attributes_summary_by_object {
665    my ($class, $base, $attribute) = @_;
666    my $attr = $base->attribute($class->type, $attribute) or do {
667        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
668        return;
669    };
670    if (!$attr->readable) {
671        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
672        return;
673    }
674    if (!$base->check_acl($class->type, $attribute, 'r')) {
675        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
676        return;
677    }
678    my %values;
679    foreach my $id ($base->list_objects($class->type)) {
680        my $obj = $base->get_object($class->type, $id);
681        my $value = $obj->_get_c_field($attribute);
682        if ($value) {
683            if (ref $value) {
684                foreach (@$value) {
685                    push(@{ $values{ $id } }, $_);
686                }
687            } else {
688                push(@{ $values{ $id } }, $value);
689            }
690        }
691    }
692    return %values;
693}
694
695=head2 find_next_numeric_id ($base, $field, $min, $max)
696
697Find next free uniq id for attribute C<$field>
698
699=cut
700
701sub find_next_numeric_id {
702    my ($class, $base, $field, $min, $max) = @_;
703    $base->attribute($class->type, $field) or return;
704    $min ||= 
705        $field eq 'uidNumber' ? 500 :
706        $field eq 'gidNumber' ? 500 :
707        1;
708    $max ||= 65635;
709    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
710        $field, $min, $max);
711    my %existsid;
712    $base->temp_switch_unexported(sub {
713        foreach ($base->list_objects($class->type)) {
714            my $obj = $base->get_object($class->type, $_) or next;
715            my $id = $obj->_get_c_field($field) or next;
716            $existsid{$id + 0} = 1;
717        }
718    }, 1);
719    $min += 0;
720    $max += 0;
721    for(my $i = $min; $i <= $max; $i++) {
722        $existsid{$i + 0} or do {
723            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
724            return $i;
725        };
726    }
727    return;
728}
729
730=head2 text_dump ($handle, $config, $base)
731
732Dump object into C<$handle>
733
734=cut
735
736sub text_dump {
737    my ($self, $handle, $config, $base) = @_;
738    print $handle $self->dump($config, $base);
739    return 1;
740}
741
742=head2 dump
743
744Return dump for tihs object
745
746=cut
747
748sub dump {
749    my ($self, $config, $base) = @_;
750
751    my $otype = $self->type;
752    $base ||= $self->base;
753    my $dump;
754    if (ref $self) {
755        $dump .= sprintf "# base %s: object %s/%s\n",
756            $base->label, $self->type, $self->id;
757    }
758    $dump .= sprintf "# %s\n", scalar(localtime);
759
760    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype,
761        $config->{only_rw} ? 'rw' : 'r')) {
762        my $oattr = ref $self ? $self->attribute($attr) : $base->attribute($otype, $attr);
763        if ($oattr->hidden) { next; }
764        if (ref $self) {
765            my $val = $self->get_c_field($attr);
766            if ($val || $config->{empty_attr}) {
767                if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
768                    $dump .= sprintf("# %s must be%s: %s\n",
769                        $attr,
770                        ($oattr->mandatory ? '' : ' empty or either'),
771                        join(', ', @allowed)
772                    );
773                }
774                my @vals = ref $val ? @{ $val } : $val;
775                foreach (@vals) {
776                    $_ ||= '';
777                    s/\r?\n/\\n/g;
778                    $dump .= sprintf("%s%s:%s\n", 
779                        $oattr->ro ? '# (ro) ' : '',
780                        $attr, $_ ? " $_" : '');
781                }
782            }
783        } else {
784            if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
785                $dump .= sprintf("# %s must be empty or either: %s\n",
786                    $attr,
787                    join(', ', @allowed)
788                );
789            }
790            $dump .= sprintf("%s%s: %s\n", 
791                $oattr->ro ? '# (ro) ' : '',
792                $attr, '');
793        }
794    }
795    return $dump;
796}
797
798=head2 ReportChange($changetype, $message, @args)
799
800Possible per database way to log changes
801
802=cut
803
804sub ReportChange {
805    my ($self, $changetype, $message, @args) = @_;
806
807    $self->base->ReportChange(
808        $self->type,
809        $self->id,
810        $self->Iid,
811        $changetype, $message, @args
812    )
813}
814
8151;
816
817__END__
818
819
820=head1 SEE ALSO
821
822L<LATMOS::Accounts::Bases>
823
824=head1 AUTHOR
825
826Thauvin Olivier, E<lt>olivier.thauvin.ipsl.fr@localdomainE<gt>
827
828=head1 COPYRIGHT AND LICENSE
829
830Copyright (C) 2009 by Thauvin Olivier
831
832This library is free software; you can redistribute it and/or modify
833it under the same terms as Perl itself, either Perl version 5.10.0 or,
834at your option, any later version of Perl 5 you may have available.
835
836=cut
Note: See TracBrowser for help on using the repository browser.