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

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

--fmt: \t means tab

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