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

Last change on this file since 984 was 965, checked in by nanardon, 12 years ago
  • avoid undef value
  • Property svn:keywords set to Id Rev
File size: 13.7 KB
Line 
1package LATMOS::Accounts::Bases::Objects;
2
3use 5.010000;
4use strict;
5use warnings;
6use LATMOS::Accounts::Log;
7use LATMOS::Accounts::Bases::Attributes;
8use Crypt::Cracklib;
9
10our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
11
12=head1 NAME
13
14LATMOS::Accounts::Bases::Objects - Base class for account objects
15
16=head1 SYNOPSIS
17
18  use LATMOS::Accounts::Bases::Objects;
19  LATMOS::Accounts::Bases::Objects->new($base, $type, $id);
20
21=head1 DESCRIPTION
22
23=head1 FUNCTIONS
24
25=cut
26
27=head2 list($base)
28
29List object supported by this module existing in base $base
30
31Must be provide by object class
32
33    sub list {
34        my ($class, $base) = @_;
35    }
36
37=cut
38
39=head2 new($base, $id)
40
41Create a new object having $id as uid.
42
43=cut
44
45sub new {
46    my ($class, $base, $id, @args) = @_;
47    # So can be call as $class->SUPER::new()
48    bless {
49        _base => $base,
50        _type => lc(($class =~ m/::([^:]*)$/)[0]),
51        _id => $id,
52    }, $class;
53}
54
55# _new($base, $type, $id, ...)
56
57# Return a new object of type $type having unique identifier
58# $id, all remaining arguments are passed to the subclass.
59
60sub _new {
61    my ($class, $base, $otype, $id, @args) = @_;
62
63    # finding perl class:
64    my $pclass = $base->_load_obj_class($otype) or return;
65    my $newobj = "$pclass"->new($base, $id, @args) or return;
66    $newobj->{_base} = $base;
67    $newobj->{_type} = lc($otype);
68    $newobj->{_id} ||= $id;
69    return $newobj;
70}
71
72=head2 _create($class, $base, $id, %data)
73
74Must create a new object in database.
75
76Is called if underling base does not override create_object
77
78    sub _create(
79        my ($class, $base, $id, %data)
80    }
81
82=cut
83
84=head2 type
85
86Return the type of the object
87
88=cut
89
90sub type {
91    my ($self) = @_;
92    if (ref $self) {
93        return $self->{_type}
94    } else {
95        return lc(($self =~ /::([^:]+)$/)[0]);
96    }
97}
98
99=head2 base
100
101Return the base handle for this object.
102
103=cut
104
105sub base {
106    return $_[0]->{_base}
107}
108
109=head2 id
110
111Must return the unique identifier for this object
112
113=cut
114
115sub id {
116    my ($self) = @_;
117    $self->{_id}
118}
119
120=head2 list_canonical_fields($for)
121
122Object shortcut to get the list of field supported by the object.
123
124=cut
125
126sub list_canonical_fields {
127    my ($self, $for) = @_;
128    $for ||= 'rw';
129    $self->_canonical_fields($for);
130}
131
132sub attribute {
133    my ($self, $attribute) = @_;
134    return LATMOS::Accounts::Bases::Attributes->new(
135        $attribute,
136        $self,
137    );
138}   
139
140sub _canonical_fields {
141    my ($class, $base, $for) = @_;
142    $for ||= 'rw';
143    my $info = $base->_get_attr_schema($class->type);
144    my @attrs = map { $base->attribute($class->type, $_) } keys %{$info || {}};
145    @attrs = grep { ! $_->ro } @attrs if($for =~ /w/);
146    @attrs = grep { $_->readable } @attrs if($for =~ /r/);
147    map { $_->name } grep { !$_->hidden }  @attrs;
148}
149
150=head2 get_field($field)
151
152Return the value for $field, must be provide by data base.
153
154    sub get_field {
155        my ($self, $field)
156    }
157
158=cut
159
160=head2 get_c_field($cfield)
161
162Return the value for canonical field $cfield.
163
164Call driver specific get_field()
165
166=cut
167
168sub get_c_field {
169    my ($self, $cfield) = @_;
170    $self->base->check_acl($self, $cfield, 'r') or do {
171        $self->base->log(LA_ERR, "Permission denied to get %s/%s",
172            $self->id, $cfield
173        );
174        return;
175    };
176    return $self->_get_c_field($cfield);
177}
178
179=head2 get_attributes($attr)
180
181Like get_c_field but always return an array
182
183=cut
184
185sub get_attributes {
186    my ($self, $cfield) = @_;
187    my $res = $self->get_c_field($cfield);
188    return ref $res ? @{ $res } : ($res);
189}
190
191sub _get_attributes {
192    my ($self, $cfield) = @_;
193    my $res = $self->_get_c_field($cfield);
194    return ref $res ? @{ $res } : ($res);
195}
196
197sub get_state {
198    my ($self, $state) = @_;
199    # hum...
200    if (defined(my $res = $self->_get_state($state))) {
201        return $res;
202    }
203    for ($state) {
204    }
205    return;
206}
207
208sub _get_state {
209    my ($self, $state) = @_;
210    return;
211}
212
213sub _get_c_field {
214    my ($self, $cfield) = @_;
215    my $attribute = $self->attribute($cfield) or do {
216        $self->base->log(LA_WARN, "Unknow attribute $cfield");
217        return;
218    };
219    $attribute->readable or do {
220        $self->base->log(LA_WARN, "Attribute $cfield is not readable");
221        return;
222    };
223    return $attribute->get; 
224}
225
226sub queryformat {
227    my ($self, $fmt) = @_;
228    $fmt =~ s/\\n/\n/g;
229    $fmt =~ s!
230        (?:%{([^:}]*)(?::([^}]+))?})
231        !
232        my $val = $self->get_c_field($1);
233        sprintf('%' . ($2 || 's'), ref $val ? join(',', @$val) : ($val||''))
234        !egx;
235    $fmt;
236}
237
238=head2 set_fields(%data)
239
240Set values for this object. %data is a list or peer field => values.
241
242    sub set_fields {
243        my ($self, %data) = @_;
244    }
245
246=cut
247
248sub check_allowed_values {
249    my ($self, $attr, $values) = @_;
250    $self->base->check_allowed_values($self->type, $attr, $values);
251}
252
253sub attr_allow_values {
254    my ($self, $attr) = @_;
255    return $self->base->obj_attr_allowed_values(
256        $self->type,
257        $attr,
258    );
259}
260
261=head2 set_c_fields(%data)
262
263Set values for this object. %data is a list or peer
264canonical field => values. Fields names are translated.
265
266=cut
267
268sub set_c_fields {
269    my ($self, %cdata) = @_;
270    foreach my $cfield (keys %cdata) {
271        $self->base->check_acl($self, $cfield, 'w') or do { 
272            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
273                $self->type, $self->id, "permission denied");
274            return;
275        };
276    }
277
278    foreach my $cfield (keys %cdata) {
279        $self->check_allowed_values($cfield, $cdata{$cfield}) or do {
280            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
281                $self->type, $self->id, "non authorized value");
282            return;
283        };
284    }
285    $self->_set_c_fields(%cdata);
286}
287
288sub _set_c_fields {
289    my ($self, %cdata) = @_;
290    my %data;
291    my $res = 0;
292    foreach my $cfield (keys %cdata) {
293        my $attribute = $self->attribute($cfield) or do {
294            $self->base->log(LA_ERR,
295                "Cannot set unsupported attribute %s to %s (%s)",
296                $cfield, $self->id, $self->type
297            );
298            return;
299        };
300        $attribute->ro and do {
301            $self->base->log(LA_ERR,
302                "Cannot set read-only attribute %s to %s (%s)",
303                $cfield, $self->id, $self->type
304            );
305            return;
306        };
307        $attribute->mandatory &&
308            (!(defined($cdata{$cfield})) || $cdata{$cfield} eq '') and do {
309            $self->base->log(LA_ERR,
310                "%s attribute cannot be empty, ignoring for object %s/%s",
311                $cfield,
312                        $self->type,
313                        $self->id,
314            );
315            return 0;
316        };
317        $res += ($attribute->set($cdata{$cfield}) || 0);
318
319    }
320    $res
321}
322
323=head2 set_password($password)
324
325Set the password into the database, $password is the clear version
326of the password.
327
328This function store it into userPassword canonical field if supported
329using crypt unix and md5 algorythm (crypt md5), the salt is 8 random
330caracters.
331
332The base driver should override it if another encryption is need.
333
334=cut
335
336sub set_password {
337    my ($self, $clear_pass) = @_;
338    if ($self->base->check_acl($self, 'userPassword', 'w')) {
339        return $self->_set_password($clear_pass);
340    } else {
341        $self->base->log(LA_ERROR, "Permission denied for %s to change its password",
342            $self->id);
343        return;
344    }
345}
346
347sub _set_password {
348    my ($self, $clear_pass) = @_;
349    if (my $attribute = $self->base->attribute($self->type, 'userPassword')) {
350        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
351        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
352        my $res = $self->set_fields($attribute->iname, crypt($clear_pass, '$1$' . $salt));
353        $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id)
354            if($res);
355        return $res;
356    } else {
357        $self->log(LA_WARN,
358            "Cannot set password: userPassword attributes is unsupported");
359    }
360}
361
362sub check_password {
363    my ( $self, $password ) = @_;
364    my $dictionary;
365
366    return fascist_check($password, $dictionary);
367}
368
369sub search {
370    my ($class, $base, @filter) = @_;
371    my @results;
372    my %parsed_filter;
373    while (my $item = shift(@filter)) {
374        # attr=foo => no extra white space !
375        # \W is false, it is possible to have two char
376        my ($attr, $mode, $val) = $item =~ /^(\w+)(?:(\W)(.+))?$/ or next;
377        if (!$mode) {
378            $mode = '~';
379            $val = shift(@filter);
380        }
381        push(
382            @{$parsed_filter{$attr}},
383            {
384                attr => $attr,
385                mode => $mode,
386                val  => $val,
387            }
388        );
389    }
390    foreach my $id ($base->list_objects($class->type)) {
391        my $obj = $base->get_object($class->type, $id);
392        my $match = 1;
393        foreach my $field (keys %parsed_filter) {
394            $base->attribute($class->type, $field) or
395                la_log LA_WARN "Unsupported attribute $field";
396            my $tmatch = 0;
397            foreach (@{$parsed_filter{$field}}) {
398                my $value = $_->{val};
399                my $fval = $obj->_get_c_field($field) || '';
400                if ($value eq '*') {
401                    if ($fval ne '') {
402                        $tmatch = 1;
403                        last;
404                    }
405                } elsif ($value eq '!') {
406                    if ($fval eq '') {
407                        $match = 1;
408                        last;
409                    }
410                } elsif ($_->{mode} eq '=') {
411                    if ($fval eq $value) {
412                        $tmatch = 1;
413                        last;
414                    }
415                } elsif($_->{mode} eq '~') {
416                    if ($fval =~ m/\Q$value\E/i) {
417                        $tmatch = 1;
418                        last;
419                    }
420                }
421            }
422            $match = 0 unless($tmatch);
423        }
424        push(@results, $id) if($match);
425    }
426    @results;
427}
428
429
430sub attributes_summary {
431    my ($class, $base, $attribute) = @_;
432    my %values;
433    foreach my $id ($base->list_objects($class->type)) {
434        my $obj = $base->get_object($class->type, $id);
435        my $value = $obj->_get_c_field($attribute);
436        if ($value) {
437            if (ref $value) {
438                foreach (@$value) {
439                    $values{$_} = 1;
440                }
441            } else {
442                $values{$value} = 1;
443            }
444        }
445    }
446    return sort(keys %values);
447}
448
449sub find_next_numeric_id {
450    my ($class, $base, $field, $min, $max) = @_;
451    $base->attribute($class->type, $field) or return;
452    $min ||= 
453        $field eq 'uidNumber' ? 500 :
454        $field eq 'gidNumber' ? 500 :
455        1;
456    $max ||= 65635;
457    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
458        $field, $min, $max);
459    my %existsid;
460    foreach ($base->list_objects($class->type)) {
461        my $obj = $base->get_object($class->type, $_) or next;
462        my $id = $obj->_get_c_field($field) or next;
463        $existsid{$id + 0} = 1;
464    }
465    $min += 0;
466    $max += 0;
467    for(my $i = $min; $i <= $max; $i++) {
468        $existsid{$i + 0} or do {
469            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
470            return $i;
471        };
472    }
473    return;
474}
475
476sub text_dump {
477    my ($self, $handle, $options, $base) = @_;
478    print $handle $self->dump($options, $base);
479    return 1;
480}
481
482sub dump {
483    my ($self, $options, $base) = @_;
484
485    my $otype = $self->type;
486    $base ||= $self->base;
487    my $dump;
488    if (ref $self) {
489        $dump .= sprintf "# base %s: object %s/%s\n",
490            $base->label, $self->type, $self->id;
491    }
492    $dump .= sprintf "# %s\n", scalar(localtime);
493
494    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype,
495        $options->{only_rw} ? 'rw' : 'r')) {
496        my $oattr = $base->attribute($otype, $attr);
497        if (ref $self) {
498            my $val = $self->get_c_field($attr);
499            if ($val || $options->{empty_attr}) {
500                if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
501                    $dump .= sprintf("# %s must be%s: %s\n",
502                        $attr,
503                        ($oattr->mandatory ? '' : ' empty or either'),
504                        join(', ', @allowed)
505                    );
506                }
507                my @vals = ref $val ? @{ $val } : $val;
508                foreach (@vals) {
509                    $_ ||= '';
510                    s/\r?\n/\\n/g;
511                    $dump .= sprintf("%s%s:%s\n", 
512                        $oattr->ro ? '# (ro) ' : '',
513                        $attr, $_ ? " $_" : '');
514                }
515            }
516        } else {
517            if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
518                $dump .= sprintf("# %s must be empty or either: %s\n",
519                    $attr,
520                    join(', ', @allowed)
521                );
522            }
523            $dump .= sprintf("%s%s: %s\n", 
524                $oattr->ro ? '# (ro) ' : '',
525                $attr, '');
526        }
527    }
528    return $dump;
529}
530
5311;
532
533__END__
534
535=head1 CANICALS FIELDS
536
537=head2 User class
538
539=head2 Group class
540
541=head1 SEE ALSO
542
543Mention other useful documentation such as the documentation of
544related modules or operating system documentation (such as man pages
545in UNIX), or any relevant external documentation such as RFCs or
546standards.
547
548If you have a mailing list set up for your module, mention it here.
549
550If you have a web site set up for your module, mention it here.
551
552=head1 AUTHOR
553
554Thauvin Olivier, E<lt>olivier.thauvin.ipsl.fr@localdomainE<gt>
555
556=head1 COPYRIGHT AND LICENSE
557
558Copyright (C) 2009 by Thauvin Olivier
559
560This library is free software; you can redistribute it and/or modify
561it under the same terms as Perl itself, either Perl version 5.10.0 or,
562at your option, any later version of Perl 5 you may have available.
563
564=cut
Note: See TracBrowser for help on using the repository browser.